Skip to content

SwiftSyntax Parser: expose parser diagnostics via C API. #22992

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 1 commit into from
Mar 2, 2019
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
58 changes: 58 additions & 0 deletions include/swift-c/SyntaxParser/SwiftSyntaxParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,64 @@ swiftparse_parse_string(swiftparse_parser_t, const char *source);
/// declarations, etc.
SWIFTPARSE_PUBLIC const char* swiftparse_syntax_structure_versioning_identifier(void);

typedef struct {
/// Represents the range for the fixit.
swiftparse_range_t range;
/// Represent the text for replacement.
const char* text;
} swiftparse_diagnostic_fixit_t;

typedef enum {
SWIFTPARSER_DIAGNOSTIC_SEVERITY_ERROR = 0,
SWIFTPARSER_DIAGNOSTIC_SEVERITY_WARNING = 1,
SWIFTPARSER_DIAGNOSTIC_SEVERITY_NOTE = 2,
} swiftparser_diagnostic_severity_t;

/// This is for the client to ask further information about a diagnostic that is
/// associated with the pointer.
/// This pointer is only valid to access from within the
/// swiftparse_diagnostic_handler_t block
typedef const void* swiftparser_diagnostic_t;

/// Invoked by the parser when a diagnostic is emitted.
typedef void(^swiftparse_diagnostic_handler_t)(swiftparser_diagnostic_t);

/// Set the \c swiftparse_diagnostic_handler_t block to be used by the parser.
///
/// It isn't required to set a \c swiftparse_diagnostic_handler_t block.
SWIFTPARSE_PUBLIC void
swiftparse_parser_set_diagnostic_handler(swiftparse_parser_t,
swiftparse_diagnostic_handler_t);

/// Get the message of a swiftparser_diagnostic_t
SWIFTPARSE_PUBLIC const char*
swiftparse_diagnostic_get_message(swiftparser_diagnostic_t);

/// Get the source location in byte offset to where the diagnostic is issued
/// in the source buffer.
SWIFTPARSE_PUBLIC
unsigned swiftparse_diagnostic_get_source_loc(swiftparser_diagnostic_t diag);

/// Get the number of fixits of a swiftparser_diagnostic_t
SWIFTPARSE_PUBLIC unsigned
swiftparse_diagnostic_get_fixit_count(swiftparser_diagnostic_t);

/// Get the fixit at the specified index of a swiftparser_diagnostic_t
SWIFTPARSE_PUBLIC swiftparse_diagnostic_fixit_t
swiftparse_diagnostic_get_fixit(swiftparser_diagnostic_t, unsigned);

/// Get the number of highlight ranges of a swiftparser_diagnostic_t
SWIFTPARSE_PUBLIC unsigned
swiftparse_diagnostic_get_range_count(swiftparser_diagnostic_t);

/// Get the highlight range at the specified index of a swiftparser_diagnostic_t
SWIFTPARSE_PUBLIC swiftparse_range_t
swiftparse_diagnostic_get_range(swiftparser_diagnostic_t, unsigned);

/// Get the severity of a swiftparser_diagnostic_t
SWIFTPARSE_PUBLIC swiftparser_diagnostic_severity_t
swiftparse_diagnostic_get_severity(swiftparser_diagnostic_t diag);

SWIFTPARSE_END_DECLS

#endif
28 changes: 28 additions & 0 deletions test/Syntax/Parser/diags.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// REQUIRES: syntax_parser_lib
// RUN: %swift-syntax-parser-test %s -dump-diags 2>&1 | %FileCheck %s

// CHECK: [[@LINE+2]]:11 Error: consecutive statements on a line must be separated by ';'
// CHECK-NEXT: ([[@LINE+1]]:11,[[@LINE+1]]:11) Fixit: ";"
let number⁚ Int
// CHECK-NEXT: [[@LINE-1]]:11 Error: operator with postfix spacing cannot start a subexpression

// CHECK-NEXT: [[@LINE+2]]:3 Error: invalid character in source file
// CHECK-NEXT: ([[@LINE+1]]:3,[[@LINE+1]]:6) Fixit: " "
5 ‒ 5
// CHECK-NEXT: [[@LINE-1]]:3 Note: unicode character '‒' looks similar to '-'; did you mean to use '-'?
// CHECK-NEXT: ([[@LINE-2]]:3,[[@LINE-2]]:6) Fixit: "-"
// CHECK-NEXT: [[@LINE-3]]:2 Error: consecutive statements on a line must be separated by ';'
// CHECK-NEXT: ([[@LINE-4]]:2,[[@LINE-4]]:2) Fixit: ";"

// CHECK-NEXT: [[@LINE+2]]:10 Error: expected ',' separator
// CHECK-NEXT: ([[@LINE+1]]:9,[[@LINE+1]]:9) Fixit: ","
if (true ꝸꝸꝸ false) {}

if (5 ‒ 5) == 0 {}
// CHECK-NEXT: [[@LINE-1]]:7 Error: invalid character in source file
// CHECK-NEXT: ([[@LINE-2]]:7,[[@LINE-2]]:10) Fixit: " "
// CHECK-NEXT: [[@LINE-3]]:7 Note: unicode character '‒' looks similar to '-'; did you mean to use '-'?
// CHECK-NEXT: ([[@LINE-4]]:7,[[@LINE-4]]:10) Fixit: "-"
// CHECK-NEXT: [[@LINE-5]]:11 Error: expected ',' separator
// CHECK-NEXT: ([[@LINE-6]]:6,[[@LINE-6]]:6) Fixit: ","
// CHECK-NEXT: 7 error(s) 0 warnings(s) 2 note(s)
153 changes: 144 additions & 9 deletions tools/libSwiftSyntaxParser/libSwiftSyntaxParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,27 @@ typedef swiftparse_trivia_piece_t CTriviaPiece;
typedef swiftparse_syntax_kind_t CSyntaxKind;

namespace {

static unsigned getByteOffset(SourceLoc Loc, SourceManager &SM,
unsigned BufferID) {
return Loc.isValid() ? SM.getLocOffsetInBuffer(Loc, BufferID) : 0;
}

static void initCRange(CRange &c_range, CharSourceRange range, SourceManager &SM,
unsigned BufferID) {
if (range.isValid()) {
c_range.offset = getByteOffset(range.getStart(), SM, BufferID);
c_range.length = range.getByteLength();
} else {
c_range.offset = 0;
c_range.length = 0;
}
}

class SynParser {
swiftparse_node_handler_t NodeHandler = nullptr;
swiftparse_node_lookup_t NodeLookup = nullptr;
swiftparse_diagnostic_handler_t DiagHandler = nullptr;

public:
swiftparse_node_handler_t getNodeHandler() const {
Expand All @@ -50,6 +68,10 @@ class SynParser {
return NodeLookup;
}

swiftparse_diagnostic_handler_t getDiagnosticHandler() const {
return DiagHandler;
}

void setNodeHandler(swiftparse_node_handler_t hdl) {
auto prevBlk = NodeHandler;
NodeHandler = Block_copy(hdl);
Expand All @@ -62,9 +84,16 @@ class SynParser {
Block_release(prevBlk);
}

void setDiagnosticHandler(swiftparse_diagnostic_handler_t hdl) {
auto prevBlk = DiagHandler;
DiagHandler = Block_copy(hdl);
Block_release(prevBlk);
}

~SynParser() {
setNodeHandler(nullptr);
setNodeLookup(nullptr);
setDiagnosticHandler(nullptr);
}

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

void makeCRange(CRange &c_range, CharSourceRange range) {
if (range.isValid()) {
c_range.offset = SM.getLocOffsetInBuffer(range.getStart(), BufferID);
c_range.length = range.getByteLength();
} else {
c_range.offset = 0;
c_range.length = 0;
}
return initCRange(c_range, range, SM, BufferID);
}

void makeCRawToken(CRawSyntaxNode &node,
Expand Down Expand Up @@ -179,8 +202,71 @@ class CLibParseActions : public SyntaxParseActions {
return {result.length, result.node};
}
};

static swiftparser_diagnostic_severity_t getSeverity(DiagnosticKind Kind) {
switch (Kind) {
case swift::DiagnosticKind::Error:
return SWIFTPARSER_DIAGNOSTIC_SEVERITY_ERROR;
case swift::DiagnosticKind::Warning:
return SWIFTPARSER_DIAGNOSTIC_SEVERITY_WARNING;
case swift::DiagnosticKind::Note:
return SWIFTPARSER_DIAGNOSTIC_SEVERITY_NOTE;
default:
llvm_unreachable("unrecognized diagnostic kind.");
}
}

struct DiagnosticDetail {
const char* Message;
unsigned Offset;
std::vector<CRange> CRanges;
swiftparser_diagnostic_severity_t Severity;
std::vector<swiftparse_diagnostic_fixit_t> AllFixits;
};

struct SynParserDiagConsumer: public DiagnosticConsumer {
SynParser &Parser;
const unsigned BufferID;
SynParserDiagConsumer(SynParser &Parser, unsigned BufferID):
Parser(Parser), BufferID(BufferID) {}
void handleDiagnostic(SourceManager &SM, SourceLoc Loc,
DiagnosticKind Kind,
StringRef FormatString,
ArrayRef<DiagnosticArgument> FormatArgs,
const DiagnosticInfo &Info) override {
assert(Kind != DiagnosticKind::Remark && "Shouldn't see this in parser.");
// The buffer where all char* will point into.
llvm::SmallString<256> Buffer;
auto getCurrentText = [&]() -> const char* {
return Buffer.data() + Buffer.size();
};
DiagnosticDetail Result;
Result.Severity = getSeverity(Kind);
Result.Offset = getByteOffset(Loc, SM, BufferID);

// Terminate each printed text with 0 so the client-side can use char* directly.
char NullTerm = '\0';
{
// Print the error message to buffer and record it.
llvm::raw_svector_ostream OS(Buffer);
Result.Message = getCurrentText();
DiagnosticEngine::formatDiagnosticText(OS, FormatString, FormatArgs);
OS << NullTerm;
}
for (auto R: Info.Ranges) {
Result.CRanges.emplace_back();
initCRange(Result.CRanges.back(), R, SM, BufferID);
}
for (auto Fixit: Info.FixIts) {
Result.AllFixits.push_back({CRange(), getCurrentText()});
initCRange(Result.AllFixits.back().range, Fixit.getRange(), SM, BufferID);
llvm::raw_svector_ostream OS(Buffer);
OS << Fixit.getText() << NullTerm;
}
Parser.getDiagnosticHandler()(static_cast<void*>(&Result));
}
};

swiftparse_client_node_t SynParser::parse(const char *source) {
SourceManager SM;
unsigned bufID = SM.addNewSourceBuffer(
Expand All @@ -193,12 +279,19 @@ swiftparse_client_node_t SynParser::parse(const char *source) {

auto parseActions =
std::make_shared<CLibParseActions>(*this, SM, bufID);
ParserUnit PU(SM, SourceFileKind::Library, bufID, langOpts,
// We have to use SourceFileKind::Main to avoid diagnostics like
// illegal_top_level_expr
ParserUnit PU(SM, SourceFileKind::Main, bufID, langOpts,
"syntax_parse_module", std::move(parseActions),
/*SyntaxCache=*/nullptr);
std::unique_ptr<SynParserDiagConsumer> pConsumer;
if (DiagHandler) {
pConsumer = llvm::make_unique<SynParserDiagConsumer>(*this, bufID);
PU.getDiagnosticEngine().addConsumer(*pConsumer);
}
return PU.parse();
}

}
//===--- C API ------------------------------------------------------------===//

swiftparse_parser_t
Expand Down Expand Up @@ -235,3 +328,45 @@ swiftparse_parse_string(swiftparse_parser_t c_parser, const char *source) {
const char* swiftparse_syntax_structure_versioning_identifier(void) {
return getSyntaxStructureVersioningIdentifier();
}

//===--------------------- C API for diagnostics -------------------------====//

void
swiftparse_parser_set_diagnostic_handler(swiftparse_parser_t c_parser,
swiftparse_diagnostic_handler_t hdl) {
SynParser *parser = static_cast<SynParser*>(c_parser);
parser->setDiagnosticHandler(hdl);
}

const char* swiftparse_diagnostic_get_message(swiftparser_diagnostic_t diag) {
return static_cast<const DiagnosticDetail*>(diag)->Message;
}

unsigned swiftparse_diagnostic_get_fixit_count(swiftparser_diagnostic_t diag) {
return static_cast<const DiagnosticDetail*>(diag)->AllFixits.size();
}

swiftparse_diagnostic_fixit_t
swiftparse_diagnostic_get_fixit(swiftparser_diagnostic_t diag, unsigned idx) {
auto allFixits = static_cast<const DiagnosticDetail*>(diag)->AllFixits;
assert(idx < allFixits.size());
return allFixits[idx];
}

unsigned swiftparse_diagnostic_get_range_count(swiftparser_diagnostic_t diag) {
return static_cast<const DiagnosticDetail*>(diag)->CRanges.size();
}

swiftparse_range_t
swiftparse_diagnostic_get_range(swiftparser_diagnostic_t diag, unsigned idx) {
return static_cast<const DiagnosticDetail*>(diag)->CRanges[idx];
}

swiftparser_diagnostic_severity_t
swiftparse_diagnostic_get_severity(swiftparser_diagnostic_t diag) {
return static_cast<const DiagnosticDetail*>(diag)->Severity;
}

unsigned swiftparse_diagnostic_get_source_loc(swiftparser_diagnostic_t diag) {
return static_cast<const DiagnosticDetail*>(diag)->Offset;
}
8 changes: 8 additions & 0 deletions tools/libSwiftSyntaxParser/libSwiftSyntaxParser.exports
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,11 @@ swiftparse_parser_dispose
swiftparse_parser_set_node_handler
swiftparse_parser_set_node_lookup
swiftparse_syntax_structure_versioning_identifier
swiftparse_parser_set_diagnostic_handler
swiftparse_diagnostic_get_message
swiftparse_diagnostic_get_fixit_count
swiftparse_diagnostic_get_fixit
swiftparse_diagnostic_get_range_count
swiftparse_diagnostic_get_range
swiftparse_diagnostic_get_severity
swiftparse_diagnostic_get_source_loc
Loading