Skip to content

Commit fa845d8

Browse files
authored
Merge pull request swiftlang#63153 from DougGregor/swift-syntax-diagnostic-style
[Diagnostics] Add a swift-syntax diagnostic style
2 parents 0f266b8 + 39c622f commit fa845d8

File tree

7 files changed

+280
-6
lines changed

7 files changed

+280
-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/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,10 @@ target_link_libraries(swiftFrontend PRIVATE
3333
swiftSymbolGraphGen)
3434

3535
set_swift_llvm_is_available(swiftFrontend)
36+
37+
if (SWIFT_SWIFT_PARSER)
38+
target_compile_definitions(swiftFrontend
39+
PRIVATE
40+
SWIFT_SWIFT_PARSER
41+
)
42+
endif()

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: 121 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,45 @@ static void annotateSnippetWithInfo(SourceManager &SM,
931952
}
932953
}
933954

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

9471007
switch (FormattingStyle) {
1008+
case DiagnosticOptions::FormattingStyle::SwiftSyntax: {
1009+
#if SWIFT_SWIFT_PARSER
1010+
if (Info.Loc.isValid()) {
1011+
// Ignore "in macro expansion" diagnostics; we want to put them
1012+
// elsewhere.
1013+
// FIXME: We should render the "in macro expansion" information in
1014+
// some other manner. Not quite sure how at this point, though.
1015+
if (Info.ID == diag::in_macro_expansion.ID)
1016+
break;
1017+
1018+
// If there are no enqueued diagnostics, they are from a different
1019+
// buffer, flush any enqueued diagnostics and create a new set.
1020+
unsigned bufferID = SM.findBufferContainingLoc(Info.Loc);
1021+
if (!queuedDiagnostics || bufferID != queuedDiagnosticsBufferID) {
1022+
flush(/*includeTrailingBreak*/ true);
1023+
1024+
// FIXME: Go parse the source file again. This is an awful hack.
1025+
auto bufferContents = SM.getEntireTextForBuffer(bufferID);
1026+
queuedSourceFile = swift_ASTGen_parseSourceFile(
1027+
bufferContents.data(), bufferContents.size(),
1028+
"module", "file.swift");
1029+
1030+
queuedBufferName = SM.getDisplayNameForLoc(Info.Loc);
1031+
queuedDiagnostics =
1032+
swift_ASTGen_createQueuedDiagnostics(queuedSourceFile);
1033+
queuedDiagnosticsBufferID = bufferID;
1034+
}
1035+
1036+
enqueueDiagnostic(queuedDiagnostics, Info, SM);
1037+
break;
1038+
}
1039+
1040+
// Fall through to print using the LLVM style when there is no source
1041+
// location.
1042+
flush(/*includeTrailingBreak*/ false);
1043+
#endif
1044+
LLVM_FALLTHROUGH;
1045+
}
1046+
9481047
case DiagnosticOptions::FormattingStyle::Swift:
9491048
if (Info.Kind == DiagnosticKind::Note && currentSnippet) {
9501049
// If this is a note and we have an in-flight message, add it to that
@@ -1000,6 +1099,27 @@ void PrintingDiagnosticConsumer::flush(bool includeTrailingBreak) {
10001099
currentSnippet.reset();
10011100
}
10021101

1102+
#if SWIFT_SWIFT_PARSER
1103+
if (queuedDiagnostics) {
1104+
Stream << "=== " << queuedBufferName << " ===\n";
1105+
1106+
char *renderedString = nullptr;
1107+
ptrdiff_t renderedStringLen = 0;
1108+
swift_ASTGen_renderQueuedDiagnostics(
1109+
queuedDiagnostics, &renderedString, &renderedStringLen);
1110+
if (renderedString) {
1111+
Stream.write(renderedString, renderedStringLen);
1112+
}
1113+
swift_ASTGen_destroyQueuedDiagnostics(queuedDiagnostics);
1114+
swift_ASTGen_destroySourceFile(queuedSourceFile);
1115+
queuedDiagnostics = nullptr;
1116+
queuedSourceFile = nullptr;
1117+
1118+
if (includeTrailingBreak)
1119+
Stream << "\n";
1120+
}
1121+
#endif
1122+
10031123
for (auto note : BufferedEducationalNotes) {
10041124
printMarkdown(note, Stream, ForceColors);
10051125
Stream << "\n";

0 commit comments

Comments
 (0)