Skip to content

Commit bdf1183

Browse files
committed
[Diagnostics] Add a swift-syntax diagnostic style
The SwiftDiagnostics module within swift-syntax has a diagnostic pretty-printer that does a nice rendering of the source code with diagnostics placed inside gaps between the code lines. Introduce another `-diagnostic-style` argument, `swift-syntax`, to bridge from the pretty-printed C++ diagnostics over to the swift-syntax diagnostics engine.
1 parent ad49d2e commit bdf1183

File tree

6 files changed

+267
-6
lines changed

6 files changed

+267
-6
lines changed

include/swift/Basic/DiagnosticOptions.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class DiagnosticOptions {
3232
VerifyAndApplyFixes
3333
} VerifyMode = NoVerify;
3434

35-
enum FormattingStyle { LLVM, Swift };
35+
enum FormattingStyle { LLVM, Swift, SwiftSyntax };
3636

3737
/// Indicates whether to allow diagnostics for \c <unknown> locations if
3838
/// \c VerifyMode is not \c NoVerify.

include/swift/Frontend/PrintingDiagnosticConsumer.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ class PrintingDiagnosticConsumer : public DiagnosticConsumer {
4545
SmallVector<std::string, 1> BufferedEducationalNotes;
4646
bool SuppressOutput = false;
4747

48+
/// swift-syntax rendering
49+
void *queuedDiagnostics = nullptr;
50+
void *queuedSourceFile = nullptr;
51+
unsigned queuedDiagnosticsBufferID;
52+
StringRef queuedBufferName;
53+
4854
public:
4955
PrintingDiagnosticConsumer(llvm::raw_ostream &stream = llvm::errs());
5056
~PrintingDiagnosticConsumer();

lib/ASTGen/Sources/ASTGen/Diagnostics.swift

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,138 @@ func emitDiagnostic(
122122
)
123123
}
124124
}
125+
126+
/// A set of queued diagnostics created by the C++ compiler and rendered
127+
/// via the swift-syntax renderer.
128+
struct QueuedDiagnostics {
129+
/// The source file in which all of the diagnostics occur.
130+
let sourceFile: SourceFileSyntax
131+
132+
/// The underlying buffer within the C++ SourceManager, which is used
133+
/// for computations of source locations.
134+
let buffer: UnsafeBufferPointer<UInt8>
135+
136+
/// The set of diagnostics.
137+
fileprivate var diagnostics: [Diagnostic] = []
138+
139+
mutating func diagnose(_ diagnostic: Diagnostic) {
140+
assert(diagnostic.node.root == sourceFile.root)
141+
diagnostics.append(diagnostic)
142+
}
143+
144+
func render() -> String {
145+
return DiagnosticsFormatter.annotatedSource(
146+
tree: sourceFile,
147+
diags: diagnostics
148+
)
149+
}
150+
}
151+
152+
/// Create a queued diagnostics structure in which we can
153+
@_cdecl("swift_ASTGen_createQueuedDiagnostics")
154+
public func createQueuedDiagnostics(
155+
sourceFilePtr: UnsafeMutablePointer<UInt8>
156+
) -> UnsafeRawPointer {
157+
return sourceFilePtr.withMemoryRebound(
158+
to: ExportedSourceFile.self, capacity: 1
159+
) { sourceFile in
160+
let ptr = UnsafeMutablePointer<QueuedDiagnostics>.allocate(capacity: 1)
161+
ptr.initialize(to: .init(
162+
sourceFile: sourceFile.pointee.syntax,
163+
buffer: sourceFile.pointee.buffer)
164+
)
165+
return UnsafeRawPointer(ptr)
166+
}
167+
}
168+
169+
/// Destroy the queued diagnostics.
170+
@_cdecl("swift_ASTGen_destroyQueuedDiagnostics")
171+
public func destroyQueuedDiagnostics(
172+
queuedDiagnosticsPtr: UnsafeMutablePointer<UInt8>
173+
) {
174+
queuedDiagnosticsPtr.withMemoryRebound(to: QueuedDiagnostics.self, capacity: 1) { queuedDiagnostics in
175+
queuedDiagnostics.deinitialize(count: 1)
176+
queuedDiagnostics.deallocate()
177+
}
178+
}
179+
180+
/// Diagnostic message used for thrown errors.
181+
fileprivate struct SimpleDiagnostic: DiagnosticMessage {
182+
let message: String
183+
184+
let severity: DiagnosticSeverity
185+
186+
var diagnosticID: MessageID {
187+
.init(domain: "SwiftCompiler", id: "SimpleDiagnostic")
188+
}
189+
}
190+
191+
extension BridgedDiagnosticSeverity {
192+
var asSeverity: DiagnosticSeverity {
193+
switch self {
194+
case .fatalError: return .error
195+
case .error: return .error
196+
case .warning: return .warning
197+
case .remark: return .warning // FIXME
198+
case .note: return .note
199+
@unknown default: return .error
200+
}
201+
}
202+
}
203+
204+
/// Add a new diagnostic to the queue.
205+
@_cdecl("swift_ASTGen_addQueuedDiagnostic")
206+
public func addQueuedDiagnostic(
207+
queuedDiagnosticsPtr: UnsafeMutableRawPointer,
208+
text: UnsafePointer<UInt8>,
209+
textLength: Int,
210+
severity: BridgedDiagnosticSeverity,
211+
position: UnsafePointer<UInt8>
212+
) {
213+
let queuedDiagnostics = queuedDiagnosticsPtr.bindMemory(
214+
to: QueuedDiagnostics.self, capacity: 1
215+
)
216+
217+
// Find the offset.
218+
let buffer = queuedDiagnostics.pointee.buffer
219+
let offset = position - buffer.baseAddress!
220+
if offset < 0 || offset >= buffer.count {
221+
return
222+
}
223+
224+
// Find the token at that offset.
225+
let sf = queuedDiagnostics.pointee.sourceFile
226+
guard let token = sf.token(at: AbsolutePosition(utf8Offset: offset)) else {
227+
return
228+
}
229+
230+
let textBuffer = UnsafeBufferPointer(start: text, count: textLength)
231+
let diagnostic = Diagnostic(
232+
node: Syntax(token),
233+
message: SimpleDiagnostic(
234+
message: String(decoding: textBuffer, as: UTF8.self),
235+
severity: severity.asSeverity
236+
)
237+
)
238+
239+
queuedDiagnostics.pointee.diagnose(diagnostic)
240+
}
241+
242+
243+
/// Render the queued diagnostics into a UTF-8 string.
244+
@_cdecl("swift_ASTGen_renderQueuedDiagnostics")
245+
public func renterQueuedDiagnostics(
246+
queuedDiagnosticsPtr: UnsafeMutablePointer<UInt8>,
247+
renderedPointer: UnsafeMutablePointer<UnsafePointer<UInt8>?>,
248+
renderedLength: UnsafeMutablePointer<Int>
249+
) {
250+
queuedDiagnosticsPtr.withMemoryRebound(to: QueuedDiagnostics.self, capacity: 1) { queuedDiagnostics in
251+
let renderedStr = DiagnosticsFormatter.annotatedSource(
252+
tree: queuedDiagnostics.pointee.sourceFile,
253+
diags: queuedDiagnostics.pointee.diagnostics
254+
)
255+
256+
(renderedPointer.pointee, renderedLength.pointee) =
257+
allocateUTF8String(renderedStr)
258+
}
259+
}

lib/ASTGen/Sources/ASTGen/Macros.swift

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@ extension SyntaxProtocol {
1616
}
1717

1818
// Otherwise, it must be one of our children.
19-
return children(viewMode: .sourceAccurate).lazy.compactMap { child in
20-
child.token(at: position)
21-
}.first
19+
for child in children(viewMode: .sourceAccurate) {
20+
if let token = child.token(at: position) {
21+
return token
22+
}
23+
}
24+
fatalError("Children of syntax node do not cover all positions in it")
2225
}
2326
}
2427

@@ -61,7 +64,7 @@ public func destroyMacro(
6164
}
6265

6366
/// Allocate a copy of the given string as a UTF-8 string.
64-
private func allocateUTF8String(
67+
func allocateUTF8String(
6568
_ string: String,
6669
nullTerminated: Bool = false
6770
) -> (UnsafePointer<UInt8>, Int) {

lib/Frontend/CompilerInvocation.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1466,6 +1466,9 @@ static bool ParseDiagnosticArgs(DiagnosticOptions &Opts, ArgList &Args,
14661466
Opts.PrintedFormattingStyle = DiagnosticOptions::FormattingStyle::LLVM;
14671467
} else if (contents == "swift") {
14681468
Opts.PrintedFormattingStyle = DiagnosticOptions::FormattingStyle::Swift;
1469+
} else if (contents == "swift-syntax") {
1470+
Opts.PrintedFormattingStyle =
1471+
DiagnosticOptions::FormattingStyle::SwiftSyntax;
14691472
} else {
14701473
Diags.diagnose(SourceLoc(), diag::error_unsupported_option_argument,
14711474
arg->getOption().getPrefixedName(), arg->getValue());

lib/Frontend/PrintingDiagnosticConsumer.cpp

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
//===----------------------------------------------------------------------===//
1616

1717
#include "swift/Frontend/PrintingDiagnosticConsumer.h"
18+
#include "swift/AST/CASTBridging.h"
1819
#include "swift/AST/DiagnosticEngine.h"
20+
#include "swift/AST/DiagnosticsCommon.h"
1921
#include "swift/Basic/LLVM.h"
2022
#include "swift/Basic/SourceManager.h"
2123
#include "swift/Markup/Markup.h"
@@ -31,6 +33,25 @@
3133
using namespace swift;
3234
using namespace swift::markup;
3335

36+
extern "C" void *swift_ASTGen_createQueuedDiagnostics(void *sourceFile);
37+
extern "C" void swift_ASTGen_destroyQueuedDiagnostics(void *queued);
38+
extern "C" void swift_ASTGen_addQueuedDiagnostic(
39+
void *queued,
40+
const char* text, ptrdiff_t textLength,
41+
BridgedDiagnosticSeverity severity,
42+
const void *sourceLoc
43+
);
44+
extern "C" void swift_ASTGen_renderQueuedDiagnostics(
45+
void *queued, char **outBuffer, ptrdiff_t *outBufferLength);
46+
47+
// FIXME: Hack because we cannot easily get to the already-parsed source
48+
// file from here. Fix this egregious oversight!
49+
extern "C" void *swift_ASTGen_parseSourceFile(const char *buffer,
50+
size_t bufferLength,
51+
const char *moduleName,
52+
const char *filename);
53+
extern "C" void swift_ASTGen_destroySourceFile(void *sourceFile);
54+
3455
namespace {
3556
class ColoredStream : public raw_ostream {
3657
raw_ostream &Underlying;
@@ -134,7 +155,7 @@ namespace {
134155

135156
void visitDocument(const Document *D) {
136157
for (const auto *Child : D->getChildren()) {
137-
if (Child->getKind() == ASTNodeKind::Paragraph) {
158+
if (Child->getKind() == markup::ASTNodeKind::Paragraph) {
138159
// Add a newline before top-level paragraphs
139160
printNewline();
140161
}
@@ -931,6 +952,43 @@ static void annotateSnippetWithInfo(SourceManager &SM,
931952
}
932953
}
933954

955+
/// Enqueue a diagnostic with ASTGen's diagnostic rendering.
956+
static void enqueueDiagnostic(
957+
void *queuedDiagnostics, const DiagnosticInfo &info, SourceManager &SM
958+
) {
959+
llvm::SmallString<256> text;
960+
{
961+
llvm::raw_svector_ostream out(text);
962+
DiagnosticEngine::formatDiagnosticText(out, info.FormatString,
963+
info.FormatArgs);
964+
}
965+
966+
BridgedDiagnosticSeverity severity;
967+
switch (info.Kind) {
968+
case DiagnosticKind::Error:
969+
severity = BridgedDiagnosticSeverity::BridgedError;
970+
break;
971+
972+
case DiagnosticKind::Warning:
973+
severity = BridgedDiagnosticSeverity::BridgedWarning;
974+
break;
975+
976+
case DiagnosticKind::Remark:
977+
severity = BridgedDiagnosticSeverity::BridgedRemark;
978+
break;
979+
980+
case DiagnosticKind::Note:
981+
severity = BridgedDiagnosticSeverity::BridgedNote;
982+
break;
983+
}
984+
985+
swift_ASTGen_addQueuedDiagnostic(
986+
queuedDiagnostics, text.data(), text.size(), severity,
987+
info.Loc.getOpaquePointerValue());
988+
989+
// FIXME: Need a way to add highlights, Fix-Its, and so on.
990+
}
991+
934992
// MARK: Main DiagnosticConsumer entrypoint.
935993
void PrintingDiagnosticConsumer::handleDiagnostic(SourceManager &SM,
936994
const DiagnosticInfo &Info) {
@@ -965,6 +1023,43 @@ void PrintingDiagnosticConsumer::handleDiagnostic(SourceManager &SM,
9651023
}
9661024
break;
9671025

1026+
case DiagnosticOptions::FormattingStyle::SwiftSyntax: {
1027+
if (Info.Loc.isValid()) {
1028+
// Ignore "in macro expansion" diagnostics; we want to put them
1029+
// elsewhere.
1030+
// FIXME: We should render the "in macro expansion" information in
1031+
// some other manner. Not quite sure how at this point, though.
1032+
if (Info.ID == diag::in_macro_expansion.ID)
1033+
break;
1034+
1035+
// If there are no enqueued diagnostics, they are from a different
1036+
// buffer, flush any enqueued diagnostics and create a new set.
1037+
unsigned bufferID = SM.findBufferContainingLoc(Info.Loc);
1038+
if (!queuedDiagnostics || bufferID != queuedDiagnosticsBufferID) {
1039+
flush(/*includeTrailingBreak*/ true);
1040+
1041+
// FIXME: Go parse the source file again. This is an awful hack.
1042+
auto bufferContents = SM.getEntireTextForBuffer(bufferID);
1043+
queuedSourceFile = swift_ASTGen_parseSourceFile(
1044+
bufferContents.data(), bufferContents.size(),
1045+
"module", "file.swift");
1046+
1047+
queuedBufferName = SM.getDisplayNameForLoc(Info.Loc);
1048+
queuedDiagnostics =
1049+
swift_ASTGen_createQueuedDiagnostics(queuedSourceFile);
1050+
queuedDiagnosticsBufferID = bufferID;
1051+
}
1052+
1053+
enqueueDiagnostic(queuedDiagnostics, Info, SM);
1054+
break;
1055+
}
1056+
1057+
// Fall through to print using the LLVM style when there is no source
1058+
// location.
1059+
flush(/*includeTrailingBreak*/ false);
1060+
LLVM_FALLTHROUGH;
1061+
}
1062+
9681063
case DiagnosticOptions::FormattingStyle::LLVM:
9691064
printDiagnostic(SM, Info);
9701065

@@ -1000,6 +1095,25 @@ void PrintingDiagnosticConsumer::flush(bool includeTrailingBreak) {
10001095
currentSnippet.reset();
10011096
}
10021097

1098+
if (queuedDiagnostics) {
1099+
Stream << "=== " << queuedBufferName << " ===\n";
1100+
1101+
char *renderedString = nullptr;
1102+
ptrdiff_t renderedStringLen = 0;
1103+
swift_ASTGen_renderQueuedDiagnostics(
1104+
queuedDiagnostics, &renderedString, &renderedStringLen);
1105+
if (renderedString) {
1106+
Stream.write(renderedString, renderedStringLen);
1107+
}
1108+
swift_ASTGen_destroyQueuedDiagnostics(queuedDiagnostics);
1109+
swift_ASTGen_destroySourceFile(queuedSourceFile);
1110+
queuedDiagnostics = nullptr;
1111+
queuedSourceFile = nullptr;
1112+
1113+
if (includeTrailingBreak)
1114+
Stream << "\n";
1115+
}
1116+
10031117
for (auto note : BufferedEducationalNotes) {
10041118
printMarkdown(note, Stream, ForceColors);
10051119
Stream << "\n";

0 commit comments

Comments
 (0)