Skip to content

Commit 740b476

Browse files
authored
Merge pull request #22992 from nkcsgexi/diagnostics-c-api
SwiftSyntax Parser: expose parser diagnostics via C API.
2 parents 56a73c9 + a86f89d commit 740b476

File tree

5 files changed

+322
-11
lines changed

5 files changed

+322
-11
lines changed

include/swift-c/SyntaxParser/SwiftSyntaxParser.h

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,64 @@ swiftparse_parse_string(swiftparse_parser_t, const char *source);
232232
/// declarations, etc.
233233
SWIFTPARSE_PUBLIC const char* swiftparse_syntax_structure_versioning_identifier(void);
234234

235+
typedef struct {
236+
/// Represents the range for the fixit.
237+
swiftparse_range_t range;
238+
/// Represent the text for replacement.
239+
const char* text;
240+
} swiftparse_diagnostic_fixit_t;
241+
242+
typedef enum {
243+
SWIFTPARSER_DIAGNOSTIC_SEVERITY_ERROR = 0,
244+
SWIFTPARSER_DIAGNOSTIC_SEVERITY_WARNING = 1,
245+
SWIFTPARSER_DIAGNOSTIC_SEVERITY_NOTE = 2,
246+
} swiftparser_diagnostic_severity_t;
247+
248+
/// This is for the client to ask further information about a diagnostic that is
249+
/// associated with the pointer.
250+
/// This pointer is only valid to access from within the
251+
/// swiftparse_diagnostic_handler_t block
252+
typedef const void* swiftparser_diagnostic_t;
253+
254+
/// Invoked by the parser when a diagnostic is emitted.
255+
typedef void(^swiftparse_diagnostic_handler_t)(swiftparser_diagnostic_t);
256+
257+
/// Set the \c swiftparse_diagnostic_handler_t block to be used by the parser.
258+
///
259+
/// It isn't required to set a \c swiftparse_diagnostic_handler_t block.
260+
SWIFTPARSE_PUBLIC void
261+
swiftparse_parser_set_diagnostic_handler(swiftparse_parser_t,
262+
swiftparse_diagnostic_handler_t);
263+
264+
/// Get the message of a swiftparser_diagnostic_t
265+
SWIFTPARSE_PUBLIC const char*
266+
swiftparse_diagnostic_get_message(swiftparser_diagnostic_t);
267+
268+
/// Get the source location in byte offset to where the diagnostic is issued
269+
/// in the source buffer.
270+
SWIFTPARSE_PUBLIC
271+
unsigned swiftparse_diagnostic_get_source_loc(swiftparser_diagnostic_t diag);
272+
273+
/// Get the number of fixits of a swiftparser_diagnostic_t
274+
SWIFTPARSE_PUBLIC unsigned
275+
swiftparse_diagnostic_get_fixit_count(swiftparser_diagnostic_t);
276+
277+
/// Get the fixit at the specified index of a swiftparser_diagnostic_t
278+
SWIFTPARSE_PUBLIC swiftparse_diagnostic_fixit_t
279+
swiftparse_diagnostic_get_fixit(swiftparser_diagnostic_t, unsigned);
280+
281+
/// Get the number of highlight ranges of a swiftparser_diagnostic_t
282+
SWIFTPARSE_PUBLIC unsigned
283+
swiftparse_diagnostic_get_range_count(swiftparser_diagnostic_t);
284+
285+
/// Get the highlight range at the specified index of a swiftparser_diagnostic_t
286+
SWIFTPARSE_PUBLIC swiftparse_range_t
287+
swiftparse_diagnostic_get_range(swiftparser_diagnostic_t, unsigned);
288+
289+
/// Get the severity of a swiftparser_diagnostic_t
290+
SWIFTPARSE_PUBLIC swiftparser_diagnostic_severity_t
291+
swiftparse_diagnostic_get_severity(swiftparser_diagnostic_t diag);
292+
235293
SWIFTPARSE_END_DECLS
236294

237295
#endif

test/Syntax/Parser/diags.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// REQUIRES: syntax_parser_lib
2+
// RUN: %swift-syntax-parser-test %s -dump-diags 2>&1 | %FileCheck %s
3+
4+
// CHECK: [[@LINE+2]]:11 Error: consecutive statements on a line must be separated by ';'
5+
// CHECK-NEXT: ([[@LINE+1]]:11,[[@LINE+1]]:11) Fixit: ";"
6+
let number Int
7+
// CHECK-NEXT: [[@LINE-1]]:11 Error: operator with postfix spacing cannot start a subexpression
8+
9+
// CHECK-NEXT: [[@LINE+2]]:3 Error: invalid character in source file
10+
// CHECK-NEXT: ([[@LINE+1]]:3,[[@LINE+1]]:6) Fixit: " "
11+
55
12+
// CHECK-NEXT: [[@LINE-1]]:3 Note: unicode character '‒' looks similar to '-'; did you mean to use '-'?
13+
// CHECK-NEXT: ([[@LINE-2]]:3,[[@LINE-2]]:6) Fixit: "-"
14+
// CHECK-NEXT: [[@LINE-3]]:2 Error: consecutive statements on a line must be separated by ';'
15+
// CHECK-NEXT: ([[@LINE-4]]:2,[[@LINE-4]]:2) Fixit: ";"
16+
17+
// CHECK-NEXT: [[@LINE+2]]:10 Error: expected ',' separator
18+
// CHECK-NEXT: ([[@LINE+1]]:9,[[@LINE+1]]:9) Fixit: ","
19+
if (true ꝸꝸꝸ false) {}
20+
21+
if (55) == 0 {}
22+
// CHECK-NEXT: [[@LINE-1]]:7 Error: invalid character in source file
23+
// CHECK-NEXT: ([[@LINE-2]]:7,[[@LINE-2]]:10) Fixit: " "
24+
// CHECK-NEXT: [[@LINE-3]]:7 Note: unicode character '‒' looks similar to '-'; did you mean to use '-'?
25+
// CHECK-NEXT: ([[@LINE-4]]:7,[[@LINE-4]]:10) Fixit: "-"
26+
// CHECK-NEXT: [[@LINE-5]]:11 Error: expected ',' separator
27+
// CHECK-NEXT: ([[@LINE-6]]:6,[[@LINE-6]]:6) Fixit: ","
28+
// CHECK-NEXT: 7 error(s) 0 warnings(s) 2 note(s)

tools/libSwiftSyntaxParser/libSwiftSyntaxParser.cpp

Lines changed: 144 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,27 @@ typedef swiftparse_trivia_piece_t CTriviaPiece;
3737
typedef swiftparse_syntax_kind_t CSyntaxKind;
3838

3939
namespace {
40+
41+
static unsigned getByteOffset(SourceLoc Loc, SourceManager &SM,
42+
unsigned BufferID) {
43+
return Loc.isValid() ? SM.getLocOffsetInBuffer(Loc, BufferID) : 0;
44+
}
45+
46+
static void initCRange(CRange &c_range, CharSourceRange range, SourceManager &SM,
47+
unsigned BufferID) {
48+
if (range.isValid()) {
49+
c_range.offset = getByteOffset(range.getStart(), SM, BufferID);
50+
c_range.length = range.getByteLength();
51+
} else {
52+
c_range.offset = 0;
53+
c_range.length = 0;
54+
}
55+
}
56+
4057
class SynParser {
4158
swiftparse_node_handler_t NodeHandler = nullptr;
4259
swiftparse_node_lookup_t NodeLookup = nullptr;
60+
swiftparse_diagnostic_handler_t DiagHandler = nullptr;
4361

4462
public:
4563
swiftparse_node_handler_t getNodeHandler() const {
@@ -50,6 +68,10 @@ class SynParser {
5068
return NodeLookup;
5169
}
5270

71+
swiftparse_diagnostic_handler_t getDiagnosticHandler() const {
72+
return DiagHandler;
73+
}
74+
5375
void setNodeHandler(swiftparse_node_handler_t hdl) {
5476
auto prevBlk = NodeHandler;
5577
NodeHandler = Block_copy(hdl);
@@ -62,9 +84,16 @@ class SynParser {
6284
Block_release(prevBlk);
6385
}
6486

87+
void setDiagnosticHandler(swiftparse_diagnostic_handler_t hdl) {
88+
auto prevBlk = DiagHandler;
89+
DiagHandler = Block_copy(hdl);
90+
Block_release(prevBlk);
91+
}
92+
6593
~SynParser() {
6694
setNodeHandler(nullptr);
6795
setNodeLookup(nullptr);
96+
setDiagnosticHandler(nullptr);
6897
}
6998

7099
swiftparse_client_node_t parse(const char *source);
@@ -102,13 +131,7 @@ class CLibParseActions : public SyntaxParseActions {
102131
}
103132

104133
void makeCRange(CRange &c_range, CharSourceRange range) {
105-
if (range.isValid()) {
106-
c_range.offset = SM.getLocOffsetInBuffer(range.getStart(), BufferID);
107-
c_range.length = range.getByteLength();
108-
} else {
109-
c_range.offset = 0;
110-
c_range.length = 0;
111-
}
134+
return initCRange(c_range, range, SM, BufferID);
112135
}
113136

114137
void makeCRawToken(CRawSyntaxNode &node,
@@ -179,8 +202,71 @@ class CLibParseActions : public SyntaxParseActions {
179202
return {result.length, result.node};
180203
}
181204
};
205+
206+
static swiftparser_diagnostic_severity_t getSeverity(DiagnosticKind Kind) {
207+
switch (Kind) {
208+
case swift::DiagnosticKind::Error:
209+
return SWIFTPARSER_DIAGNOSTIC_SEVERITY_ERROR;
210+
case swift::DiagnosticKind::Warning:
211+
return SWIFTPARSER_DIAGNOSTIC_SEVERITY_WARNING;
212+
case swift::DiagnosticKind::Note:
213+
return SWIFTPARSER_DIAGNOSTIC_SEVERITY_NOTE;
214+
default:
215+
llvm_unreachable("unrecognized diagnostic kind.");
216+
}
182217
}
183218

219+
struct DiagnosticDetail {
220+
const char* Message;
221+
unsigned Offset;
222+
std::vector<CRange> CRanges;
223+
swiftparser_diagnostic_severity_t Severity;
224+
std::vector<swiftparse_diagnostic_fixit_t> AllFixits;
225+
};
226+
227+
struct SynParserDiagConsumer: public DiagnosticConsumer {
228+
SynParser &Parser;
229+
const unsigned BufferID;
230+
SynParserDiagConsumer(SynParser &Parser, unsigned BufferID):
231+
Parser(Parser), BufferID(BufferID) {}
232+
void handleDiagnostic(SourceManager &SM, SourceLoc Loc,
233+
DiagnosticKind Kind,
234+
StringRef FormatString,
235+
ArrayRef<DiagnosticArgument> FormatArgs,
236+
const DiagnosticInfo &Info) override {
237+
assert(Kind != DiagnosticKind::Remark && "Shouldn't see this in parser.");
238+
// The buffer where all char* will point into.
239+
llvm::SmallString<256> Buffer;
240+
auto getCurrentText = [&]() -> const char* {
241+
return Buffer.data() + Buffer.size();
242+
};
243+
DiagnosticDetail Result;
244+
Result.Severity = getSeverity(Kind);
245+
Result.Offset = getByteOffset(Loc, SM, BufferID);
246+
247+
// Terminate each printed text with 0 so the client-side can use char* directly.
248+
char NullTerm = '\0';
249+
{
250+
// Print the error message to buffer and record it.
251+
llvm::raw_svector_ostream OS(Buffer);
252+
Result.Message = getCurrentText();
253+
DiagnosticEngine::formatDiagnosticText(OS, FormatString, FormatArgs);
254+
OS << NullTerm;
255+
}
256+
for (auto R: Info.Ranges) {
257+
Result.CRanges.emplace_back();
258+
initCRange(Result.CRanges.back(), R, SM, BufferID);
259+
}
260+
for (auto Fixit: Info.FixIts) {
261+
Result.AllFixits.push_back({CRange(), getCurrentText()});
262+
initCRange(Result.AllFixits.back().range, Fixit.getRange(), SM, BufferID);
263+
llvm::raw_svector_ostream OS(Buffer);
264+
OS << Fixit.getText() << NullTerm;
265+
}
266+
Parser.getDiagnosticHandler()(static_cast<void*>(&Result));
267+
}
268+
};
269+
184270
swiftparse_client_node_t SynParser::parse(const char *source) {
185271
SourceManager SM;
186272
unsigned bufID = SM.addNewSourceBuffer(
@@ -193,12 +279,19 @@ swiftparse_client_node_t SynParser::parse(const char *source) {
193279

194280
auto parseActions =
195281
std::make_shared<CLibParseActions>(*this, SM, bufID);
196-
ParserUnit PU(SM, SourceFileKind::Library, bufID, langOpts,
282+
// We have to use SourceFileKind::Main to avoid diagnostics like
283+
// illegal_top_level_expr
284+
ParserUnit PU(SM, SourceFileKind::Main, bufID, langOpts,
197285
"syntax_parse_module", std::move(parseActions),
198286
/*SyntaxCache=*/nullptr);
287+
std::unique_ptr<SynParserDiagConsumer> pConsumer;
288+
if (DiagHandler) {
289+
pConsumer = llvm::make_unique<SynParserDiagConsumer>(*this, bufID);
290+
PU.getDiagnosticEngine().addConsumer(*pConsumer);
291+
}
199292
return PU.parse();
200293
}
201-
294+
}
202295
//===--- C API ------------------------------------------------------------===//
203296

204297
swiftparse_parser_t
@@ -235,3 +328,45 @@ swiftparse_parse_string(swiftparse_parser_t c_parser, const char *source) {
235328
const char* swiftparse_syntax_structure_versioning_identifier(void) {
236329
return getSyntaxStructureVersioningIdentifier();
237330
}
331+
332+
//===--------------------- C API for diagnostics -------------------------====//
333+
334+
void
335+
swiftparse_parser_set_diagnostic_handler(swiftparse_parser_t c_parser,
336+
swiftparse_diagnostic_handler_t hdl) {
337+
SynParser *parser = static_cast<SynParser*>(c_parser);
338+
parser->setDiagnosticHandler(hdl);
339+
}
340+
341+
const char* swiftparse_diagnostic_get_message(swiftparser_diagnostic_t diag) {
342+
return static_cast<const DiagnosticDetail*>(diag)->Message;
343+
}
344+
345+
unsigned swiftparse_diagnostic_get_fixit_count(swiftparser_diagnostic_t diag) {
346+
return static_cast<const DiagnosticDetail*>(diag)->AllFixits.size();
347+
}
348+
349+
swiftparse_diagnostic_fixit_t
350+
swiftparse_diagnostic_get_fixit(swiftparser_diagnostic_t diag, unsigned idx) {
351+
auto allFixits = static_cast<const DiagnosticDetail*>(diag)->AllFixits;
352+
assert(idx < allFixits.size());
353+
return allFixits[idx];
354+
}
355+
356+
unsigned swiftparse_diagnostic_get_range_count(swiftparser_diagnostic_t diag) {
357+
return static_cast<const DiagnosticDetail*>(diag)->CRanges.size();
358+
}
359+
360+
swiftparse_range_t
361+
swiftparse_diagnostic_get_range(swiftparser_diagnostic_t diag, unsigned idx) {
362+
return static_cast<const DiagnosticDetail*>(diag)->CRanges[idx];
363+
}
364+
365+
swiftparser_diagnostic_severity_t
366+
swiftparse_diagnostic_get_severity(swiftparser_diagnostic_t diag) {
367+
return static_cast<const DiagnosticDetail*>(diag)->Severity;
368+
}
369+
370+
unsigned swiftparse_diagnostic_get_source_loc(swiftparser_diagnostic_t diag) {
371+
return static_cast<const DiagnosticDetail*>(diag)->Offset;
372+
}

tools/libSwiftSyntaxParser/libSwiftSyntaxParser.exports

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,11 @@ swiftparse_parser_dispose
44
swiftparse_parser_set_node_handler
55
swiftparse_parser_set_node_lookup
66
swiftparse_syntax_structure_versioning_identifier
7+
swiftparse_parser_set_diagnostic_handler
8+
swiftparse_diagnostic_get_message
9+
swiftparse_diagnostic_get_fixit_count
10+
swiftparse_diagnostic_get_fixit
11+
swiftparse_diagnostic_get_range_count
12+
swiftparse_diagnostic_get_range
13+
swiftparse_diagnostic_get_severity
14+
swiftparse_diagnostic_get_source_loc

0 commit comments

Comments
 (0)