Skip to content

[Diagnostics] Print category footnotes at the end of translation #79874

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/swift/AST/DiagnosticBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ class DiagnosticBridge {
/// A queued up source file known to the queued diagnostics.
using QueuedBuffer = void *;

/// Per-frontend state maintained on the Swift side.
void *perFrontendState = nullptr;

/// The queued diagnostics structure.
void *queuedDiagnostics = nullptr;
llvm::DenseMap<unsigned, QueuedBuffer> queuedBuffers;
Expand All @@ -56,6 +59,9 @@ class DiagnosticBridge {
static SmallVector<unsigned, 1> getSourceBufferStack(SourceManager &sourceMgr,
SourceLoc loc);

/// Print the category footnotes as part of teardown.
void printCategoryFootnotes(llvm::raw_ostream &os, bool forceColors);

DiagnosticBridge() = default;
~DiagnosticBridge();

Expand Down
9 changes: 8 additions & 1 deletion include/swift/Bridging/ASTGen.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ void swift_ASTGen_addQueuedSourceFile(
void *_Nonnull sourceFile, const uint8_t *_Nonnull displayNamePtr,
intptr_t displayNameLength, ssize_t parentID, ssize_t positionInParent);
void swift_ASTGen_addQueuedDiagnostic(
void *_Nonnull queued, const char *_Nonnull text, ptrdiff_t textLength,
void *_Nonnull queued, void *_Nonnull state,
const char *_Nonnull text, ptrdiff_t textLength,
BridgedDiagnosticSeverity severity, const void *_Nullable sourceLoc,
const char *_Nullable categoryName, ptrdiff_t categoryNameLength,
const char *_Nullable documentationPath,
Expand All @@ -37,6 +38,12 @@ void swift_ASTGen_renderQueuedDiagnostics(
void *_Nonnull queued, ssize_t contextSize, ssize_t colorize,
BridgedStringRef *_Nonnull renderedString);

void *_Nonnull swift_ASTGen_createPerFrontendDiagnosticState();
void swift_ASTGen_destroyPerFrontendDiagnosticState(void * _Nonnull state);
void swift_ASTGen_renderCategoryFootnotes(
void * _Nonnull state, ssize_t colorize,
BridgedStringRef *_Nonnull renderedString);

// FIXME: Hack because we cannot easily get to the already-parsed source
// file from here. Fix this egregious oversight!
void *_Nullable swift_ASTGen_parseSourceFile(BridgedStringRef buffer,
Expand Down
33 changes: 30 additions & 3 deletions lib/AST/DiagnosticBridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ using namespace swift;
#if SWIFT_BUILD_SWIFT_SYNTAX
/// Enqueue a diagnostic with ASTGen's diagnostic rendering.
static void addQueueDiagnostic(void *queuedDiagnostics,
void *perFrontendState,
const DiagnosticInfo &info, SourceManager &SM) {
llvm::SmallString<256> text;
{
Expand Down Expand Up @@ -69,7 +70,8 @@ static void addQueueDiagnostic(void *queuedDiagnostics,
documentationPath = info.EducationalNotePaths[0];

// FIXME: Translate Fix-Its.
swift_ASTGen_addQueuedDiagnostic(queuedDiagnostics, text.data(), text.size(),
swift_ASTGen_addQueuedDiagnostic(queuedDiagnostics, perFrontendState,
text.data(), text.size(),
severity, info.Loc.getOpaquePointerValue(),
info.Category.data(),
info.Category.size(),
Expand All @@ -82,20 +84,25 @@ static void addQueueDiagnostic(void *queuedDiagnostics,
// argument to `swift_ASTGen_addQueuedDiagnostic` but that requires
// bridging of `Note` structure and new serialization.
for (auto *childNote : info.ChildDiagnosticInfo) {
addQueueDiagnostic(queuedDiagnostics, *childNote, SM);
addQueueDiagnostic(queuedDiagnostics, perFrontendState, *childNote, SM);
}
}

void DiagnosticBridge::enqueueDiagnostic(SourceManager &SM,
const DiagnosticInfo &Info,
unsigned innermostBufferID) {
// If we didn't have per-frontend state before, create it now.
if (!perFrontendState) {
perFrontendState = swift_ASTGen_createPerFrontendDiagnosticState();
}

// If there are no enqueued diagnostics, or we have hit a non-note
// diagnostic, flush any enqueued diagnostics and start fresh.
if (!queuedDiagnostics)
queuedDiagnostics = swift_ASTGen_createQueuedDiagnostics();

queueBuffer(SM, innermostBufferID);
addQueueDiagnostic(queuedDiagnostics, Info, SM);
addQueueDiagnostic(queuedDiagnostics, perFrontendState, Info, SM);
}

void DiagnosticBridge::flush(llvm::raw_ostream &OS, bool includeTrailingBreak,
Expand All @@ -120,6 +127,22 @@ void DiagnosticBridge::flush(llvm::raw_ostream &OS, bool includeTrailingBreak,
OS << "\n";
}

void DiagnosticBridge::printCategoryFootnotes(llvm::raw_ostream &os,
bool forceColors) {
if (!perFrontendState)
return;

BridgedStringRef bridgedRenderedString{nullptr, 0};
swift_ASTGen_renderCategoryFootnotes(
perFrontendState, forceColors ? 1 : 0, &bridgedRenderedString);

auto renderedString = bridgedRenderedString.unbridged();
if (auto renderedData = renderedString.data()) {
os.write(renderedData, renderedString.size());
swift_ASTGen_freeBridgedString(renderedString);
}
}

void *DiagnosticBridge::getSourceFileSyntax(SourceManager &sourceMgr,
unsigned bufferID,
StringRef displayName) {
Expand Down Expand Up @@ -199,6 +222,10 @@ DiagnosticBridge::getSourceBufferStack(SourceManager &sourceMgr,
}

DiagnosticBridge::~DiagnosticBridge() {
if (perFrontendState) {
swift_ASTGen_destroyPerFrontendDiagnosticState(perFrontendState);
}

assert(!queuedDiagnostics && "unflushed diagnostics");
for (const auto &sourceFileSyntax : sourceFileSyntax) {
swift_ASTGen_destroySourceFile(sourceFileSyntax.second);
Expand Down
54 changes: 54 additions & 0 deletions lib/ASTGen/Sources/ASTGen/DiagnosticsBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ import BasicBridging
import SwiftDiagnostics
import SwiftSyntax

fileprivate struct PerFrontendDiagnosticState {
/// The set of categories that were referenced by a diagnostic.
var referencedCategories: Set<DiagnosticCategory> = []
}

fileprivate func emitDiagnosticParts(
diagnosticEngine: BridgedDiagnosticEngine,
sourceFileBuffer: UnsafeBufferPointer<UInt8>,
Expand Down Expand Up @@ -235,6 +240,7 @@ public func addQueuedSourceFile(
@_cdecl("swift_ASTGen_addQueuedDiagnostic")
public func addQueuedDiagnostic(
queuedDiagnosticsPtr: UnsafeMutableRawPointer,
perFrontendDiagnosticStatePtr: UnsafeMutableRawPointer,
text: UnsafePointer<UInt8>,
textLength: Int,
severity: BridgedDiagnosticSeverity,
Expand All @@ -250,6 +256,10 @@ public func addQueuedDiagnostic(
to: QueuedDiagnostics.self
)

let diagnosticState = perFrontendDiagnosticStatePtr.assumingMemoryBound(
to: PerFrontendDiagnosticState.self
)

guard let rawPosition = cLoc.getOpaquePointerValue() else {
return
}
Expand Down Expand Up @@ -375,6 +385,11 @@ public func addQueuedDiagnostic(
)
}

// Note that we referenced this category.
if let category {
diagnosticState.pointee.referencedCategories.insert(category)
}

let textBuffer = UnsafeBufferPointer(start: text, count: textLength)
let diagnostic = Diagnostic(
node: node,
Expand Down Expand Up @@ -431,3 +446,42 @@ extension String {
return false
}
}

@_cdecl("swift_ASTGen_createPerFrontendDiagnosticState")
public func createPerFrontendDiagnosticState() -> UnsafeMutableRawPointer {
let ptr = UnsafeMutablePointer<PerFrontendDiagnosticState>.allocate(capacity: 1)
ptr.initialize(to: .init())
return UnsafeMutableRawPointer(ptr)
}

@_cdecl("swift_ASTGen_destroyPerFrontendDiagnosticState")
public func destroyPerFrontendDiagnosticState(
statePtr: UnsafeMutableRawPointer
) {
let state = statePtr.assumingMemoryBound(to: PerFrontendDiagnosticState.self)
state.deinitialize(count: 1)
state.deallocate()
}

@_cdecl("swift_ASTGen_renderCategoryFootnotes")
public func renderCategoryFootnotes(
statePtr: UnsafeMutableRawPointer,
colorize: Int,
renderedStringOutPtr: UnsafeMutablePointer<BridgedStringRef>
) {
let state = statePtr.assumingMemoryBound(to: PerFrontendDiagnosticState.self)
let formatter = DiagnosticsFormatter(contextSize: 0, colorize: colorize != 0)
var renderedStr = formatter.categoryFootnotes(
Array(state.pointee.referencedCategories),
leadingText: "\n"
)

if !renderedStr.isEmpty {
renderedStr += "\n"
}

renderedStringOutPtr.pointee = allocateBridgedString(renderedStr)

// Clear out categories so we start fresh.
state.pointee.referencedCategories = []
}
3 changes: 3 additions & 0 deletions lib/ASTGen/Sources/ASTGen/Exprs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,9 @@ extension ASTGenVisitor {
additionalTrailingClosures: nil
)
).asExpr

case .method(_):
fatalError("unimplemented")
}
}

Expand Down
6 changes: 6 additions & 0 deletions lib/Frontend/PrintingDiagnosticConsumer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,12 @@ void PrintingDiagnosticConsumer::flush(bool includeTrailingBreak) {
bool PrintingDiagnosticConsumer::finishProcessing() {
// If there's an in-flight snippet, flush it.
flush(false);

#if SWIFT_BUILD_SWIFT_SYNTAX
// Print out footnotes for any category that was referenced.
DiagBridge.printCategoryFootnotes(Stream, ForceColors);
#endif

return false;
}

Expand Down
17 changes: 7 additions & 10 deletions test/diagnostics/print-diagnostic-groups.swift
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
// RUN: %target-swift-frontend -typecheck -diagnostic-style llvm -print-diagnostic-groups %s 2>&1 | %FileCheck %s --check-prefix=CHECK
// RUN: %target-swift-frontend -typecheck %s 2>&1 | %FileCheck %s --check-prefix=CHECK
// REQUIRES: swift_swift_parser

// CHECK: warning: file 'print-diagnostic-groups.swift' is part of module 'main'; ignoring import{{$}}
import main

// This test checks that "-print-diagnostic-groups" prints the diagnostic group
// if it exists, and prints nothing if it does not.

// This test checks that we get diagnostic groups as part of the printed output.

@available(*, deprecated, renamed: "bar2")
func bar() {
}

// CHECK: warning: 'bar()' is deprecated: renamed to 'bar2' [DeprecatedDeclaration]{{$}}
// CHECK: warning: 'bar()' is deprecated: renamed to 'bar2' [#DeprecatedDeclaration]{{$}}
bar()


func foo() {
// CHECK: warning: initialization of immutable value 'x' was never used; consider replacing with assignment to '_' or removing it{{$}}
let x = 42
}
// CHECK: [#DeprecatedDeclarations]: <{{.*}}deprecated-declaration.md>