Skip to content

Commit 5eff438

Browse files
committed
Support inline diagnostics in CommandReturnObject
and implement them for dwim-print (a.k.a. `p`) as an example. The next step will be to expose them as structured data in SBCommandReturnObject.
1 parent 99f527d commit 5eff438

14 files changed

+168
-62
lines changed

lldb/include/lldb/Expression/DiagnosticManager.h

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,35 +23,6 @@
2323

2424
namespace lldb_private {
2525

26-
/// A compiler-independent representation of a Diagnostic. Expression
27-
/// evaluation failures often have more than one diagnostic that a UI
28-
/// layer might want to render differently, for example to colorize
29-
/// it.
30-
///
31-
/// Running example:
32-
/// (lldb) expr 1+foo
33-
/// error: <user expression 0>:1:3: use of undeclared identifier 'foo'
34-
/// 1+foo
35-
/// ^
36-
struct DiagnosticDetail {
37-
struct SourceLocation {
38-
FileSpec file;
39-
unsigned line = 0;
40-
uint16_t column = 0;
41-
uint16_t length = 0;
42-
bool hidden = false;
43-
bool in_user_input = false;
44-
};
45-
/// Contains {{}, 1, 3, 3, true} in the example above.
46-
std::optional<SourceLocation> source_location;
47-
/// Contains eSeverityError in the example above.
48-
lldb::Severity severity = lldb::eSeverityInfo;
49-
/// Contains "use of undeclared identifier 'x'" in the example above.
50-
std::string message;
51-
/// Contains the fully rendered error message.
52-
std::string rendered;
53-
};
54-
5526
/// An llvm::Error used to communicate diagnostics in Status. Multiple
5627
/// diagnostics may be chained in an llvm::ErrorList.
5728
class ExpressionError
@@ -65,7 +36,9 @@ class ExpressionError
6536
ExpressionError(lldb::ExpressionResults result, std::string msg,
6637
std::vector<DiagnosticDetail> details = {});
6738
std::string message() const override;
68-
llvm::ArrayRef<DiagnosticDetail> GetDetails() const { return m_details; }
39+
llvm::ArrayRef<DiagnosticDetail> GetDetails() const override {
40+
return m_details;
41+
}
6942
std::error_code convertToErrorCode() const override;
7043
void log(llvm::raw_ostream &OS) const override;
7144
std::unique_ptr<CloneableError> Clone() const override;

lldb/include/lldb/Interpreter/CommandReturnObject.h

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,17 @@ class CommandReturnObject {
2929

3030
~CommandReturnObject() = default;
3131

32+
llvm::StringRef GetInlineDiagnosticsData(unsigned indent,
33+
llvm::StringRef command);
34+
3235
llvm::StringRef GetOutputData() {
3336
lldb::StreamSP stream_sp(m_out_stream.GetStreamAtIndex(eStreamStringIndex));
3437
if (stream_sp)
3538
return std::static_pointer_cast<StreamString>(stream_sp)->GetString();
3639
return llvm::StringRef();
3740
}
3841

39-
llvm::StringRef GetErrorData() {
40-
lldb::StreamSP stream_sp(m_err_stream.GetStreamAtIndex(eStreamStringIndex));
41-
if (stream_sp)
42-
return std::static_pointer_cast<StreamString>(stream_sp)->GetString();
43-
return llvm::StringRef();
44-
}
42+
llvm::StringRef GetErrorData();
4543

4644
Stream &GetOutputStream() {
4745
// Make sure we at least have our normal string stream output stream
@@ -135,6 +133,8 @@ class CommandReturnObject {
135133

136134
void SetError(llvm::Error error);
137135

136+
void SetUserInput(llvm::StringRef user_input) { m_user_input = user_input; }
137+
138138
lldb::ReturnStatus GetStatus() const;
139139

140140
void SetStatus(lldb::ReturnStatus status);
@@ -160,6 +160,9 @@ class CommandReturnObject {
160160

161161
StreamTee m_out_stream;
162162
StreamTee m_err_stream;
163+
std::vector<DiagnosticDetail> m_diagnostics;
164+
StreamString m_diag_stream;
165+
std::string m_user_input;
163166

164167
lldb::ReturnStatus m_status = lldb::eReturnStatusStarted;
165168

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//===-- DiagnosticsRendering.h ----------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLDB_UTILITY_DIAGNOSTICSRENDERING_H
10+
#define LLDB_UTILITY_DIAGNOSTICSRENDERING_H
11+
12+
#include "lldb/Utility/Status.h"
13+
#include "lldb/Utility/Stream.h"
14+
#include "llvm/Support/WithColor.h"
15+
16+
namespace lldb_private {
17+
18+
llvm::raw_ostream &PrintSeverity(Stream &stream, lldb::Severity severity);
19+
20+
void RenderDiagnosticDetails(Stream &stream,
21+
std::optional<uint16_t> offset_in_command,
22+
bool show_inline,
23+
llvm::ArrayRef<DiagnosticDetail> details);
24+
} // namespace lldb_private
25+
#endif

lldb/include/lldb/Utility/Status.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#ifndef LLDB_UTILITY_STATUS_H
1010
#define LLDB_UTILITY_STATUS_H
1111

12+
#include "lldb/Utility/FileSpec.h"
1213
#include "lldb/lldb-defines.h"
1314
#include "lldb/lldb-enumerations.h"
1415
#include "llvm/ADT/StringRef.h"
@@ -26,6 +27,48 @@ class raw_ostream;
2627

2728
namespace lldb_private {
2829

30+
/// A compiler-independent representation of an \c
31+
/// lldb_private::Diagnostic. Expression evaluation failures often
32+
/// have more than one diagnostic that a UI layer might want to render
33+
/// differently, for example to colorize it.
34+
///
35+
/// Running example:
36+
/// (lldb) expr 1 + foo
37+
/// error: <user expression 0>:1:3: use of undeclared identifier 'foo'
38+
/// 1 + foo
39+
/// ^~~
40+
struct DiagnosticDetail {
41+
/// A source location consisting of a file name and position.
42+
struct SourceLocation {
43+
/// \c "<user expression 0>" in the example above.
44+
FileSpec file;
45+
/// \c 1 in the example above.
46+
unsigned line = 0;
47+
/// \c 5 in the example above.
48+
uint16_t column = 0;
49+
/// \c 3 in the example above.
50+
uint16_t length = 0;
51+
/// Whether this source location should be surfaced to the
52+
/// user. For example, syntax errors diagnosed in LLDB's
53+
/// expression wrapper code have this set to true.
54+
bool hidden = false;
55+
/// Whether this source location refers to something the user
56+
/// typed as part of the command, i.e., if this qualifies for
57+
/// inline display, or if the source line would need to be echoed
58+
/// again for the message to make sense.
59+
bool in_user_input = false;
60+
};
61+
/// Contains this diagnostic's source location, if applicable.
62+
std::optional<SourceLocation> source_location;
63+
/// Contains \c eSeverityError in the example above.
64+
lldb::Severity severity = lldb::eSeverityInfo;
65+
/// Contains "use of undeclared identifier 'foo'" in the example above.
66+
std::string message;
67+
/// Contains the fully rendered error message, without "error: ",
68+
/// but including the source context.
69+
std::string rendered;
70+
};
71+
2972
const char *ExpressionResultAsCString(lldb::ExpressionResults result);
3073

3174
/// Going a bit against the spirit of llvm::Error,
@@ -86,6 +129,7 @@ class ExpressionErrorBase
86129
ExpressionErrorBase(std::error_code ec, std::string msg = {})
87130
: ErrorInfo(ec) {}
88131
lldb::ErrorType GetErrorType() const override;
132+
virtual llvm::ArrayRef<DiagnosticDetail> GetDetails() const;
89133
static char ID;
90134
};
91135

lldb/source/Commands/CommandObjectDWIMPrint.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,12 +188,14 @@ void CommandObjectDWIMPrint::DoExecute(StringRef command,
188188
ValueObjectSP valobj_sp;
189189
std::string fixed_expression;
190190

191+
result.SetUserInput(expr);
191192
ExpressionResults expr_result = target.EvaluateExpression(
192193
expr, exe_scope, valobj_sp, eval_options, &fixed_expression);
193194

194195
// Only mention Fix-Its if the expression evaluator applied them.
195196
// Compiler errors refer to the final expression after applying Fix-It(s).
196197
if (!fixed_expression.empty() && target.GetEnableNotifyAboutFixIts()) {
198+
result.SetUserInput(fixed_expression);
197199
Stream &error_stream = result.GetErrorStream();
198200
error_stream << " Evaluated this expression after applying Fix-It(s):\n";
199201
error_stream << " " << fixed_expression << "\n";

lldb/source/Commands/CommandObjectExpression.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "CommandObjectExpression.h"
10-
#include "DiagnosticRendering.h"
1110
#include "lldb/Core/Debugger.h"
12-
#include "lldb/Expression/DiagnosticManager.h"
1311
#include "lldb/Expression/ExpressionVariable.h"
1412
#include "lldb/Expression/REPL.h"
1513
#include "lldb/Expression/UserExpression.h"
@@ -22,6 +20,7 @@
2220
#include "lldb/Target/Process.h"
2321
#include "lldb/Target/StackFrame.h"
2422
#include "lldb/Target/Target.h"
23+
#include "lldb/Utility/DiagnosticsRendering.h"
2524
#include "lldb/lldb-enumerations.h"
2625
#include "lldb/lldb-private-enumerations.h"
2726

@@ -490,7 +489,7 @@ bool CommandObjectExpression::EvaluateExpression(llvm::StringRef expr,
490489
std::vector<DiagnosticDetail> details;
491490
llvm::consumeError(llvm::handleErrors(
492491
result_valobj_sp->GetError().ToError(),
493-
[&](ExpressionError &error) { details = error.GetDetails(); }));
492+
[&](ExpressionErrorBase &error) { details = error.GetDetails(); }));
494493
// Find the position of the expression in the command.
495494
std::optional<uint16_t> expr_pos;
496495
size_t nchar = m_original_command.find(expr);

lldb/source/Interpreter/CommandInterpreter.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2069,7 +2069,7 @@ bool CommandInterpreter::HandleCommand(const char *command_line,
20692069
remainder.c_str());
20702070

20712071
// To test whether or not transcript should be saved, `transcript_item` is
2072-
// used instead of `GetSaveTrasncript()`. This is because the latter will
2072+
// used instead of `GetSaveTranscript()`. This is because the latter will
20732073
// fail when the command is "settings set interpreter.save-transcript true".
20742074
if (transcript_item) {
20752075
transcript_item->AddStringItem("commandName", cmd_obj->GetCommandName());
@@ -3180,15 +3180,24 @@ void CommandInterpreter::IOHandlerInputComplete(IOHandler &io_handler,
31803180
if ((result.Succeeded() &&
31813181
io_handler.GetFlags().Test(eHandleCommandFlagPrintResult)) ||
31823182
io_handler.GetFlags().Test(eHandleCommandFlagPrintErrors)) {
3183-
// Display any STDOUT/STDERR _prior_ to emitting the command result text
31843183
GetProcessOutput();
31853184

3185+
// Display any inline diagnostics first.
3186+
if (!result.GetImmediateErrorStream() &&
3187+
GetDebugger().GetShowInlineDiagnostics() &&
3188+
line.find('\n') == std::string::npos) {
3189+
unsigned indent = GetDebugger().GetPrompt().size();
3190+
llvm::StringRef diags = result.GetInlineDiagnosticsData(indent, line);
3191+
PrintCommandOutput(io_handler, diags, true);
3192+
}
3193+
3194+
// Display any STDOUT/STDERR _prior_ to emitting the command result text.
31863195
if (!result.GetImmediateOutputStream()) {
31873196
llvm::StringRef output = result.GetOutputData();
31883197
PrintCommandOutput(io_handler, output, true);
31893198
}
31903199

3191-
// Now emit the command error text from the command we just executed
3200+
// Now emit the command error text from the command we just executed.
31923201
if (!result.GetImmediateErrorStream()) {
31933202
llvm::StringRef error = result.GetErrorData();
31943203
PrintCommandOutput(io_handler, error, false);

lldb/source/Interpreter/CommandReturnObject.cpp

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "lldb/Interpreter/CommandReturnObject.h"
1010

11+
#include "lldb/Utility/DiagnosticsRendering.h"
1112
#include "lldb/Utility/Status.h"
1213
#include "lldb/Utility/StreamString.h"
1314

@@ -112,8 +113,46 @@ void CommandReturnObject::SetError(Status error) {
112113
}
113114

114115
void CommandReturnObject::SetError(llvm::Error error) {
115-
if (error)
116+
// Retrieve any diagnostics.
117+
error = llvm::handleErrors(std::move(error), [&](ExpressionErrorBase &error) {
118+
SetStatus(eReturnStatusFailed);
119+
m_diagnostics = error.GetDetails();
120+
});
121+
if (error) {
116122
AppendError(llvm::toString(std::move(error)));
123+
}
124+
}
125+
126+
llvm::StringRef
127+
CommandReturnObject::GetInlineDiagnosticsData(unsigned indent,
128+
llvm::StringRef command) {
129+
size_t nchar = command.find(m_user_input);
130+
if (nchar == llvm::StringRef::npos)
131+
return {};
132+
133+
indent += nchar;
134+
RenderDiagnosticDetails(m_diag_stream, indent, true, m_diagnostics);
135+
// Duplex the diagnostics to the secondary stream (but not inlined).
136+
if (auto stream_sp = m_err_stream.GetStreamAtIndex(eStreamStringIndex))
137+
RenderDiagnosticDetails(*stream_sp, std::nullopt, false, m_diagnostics);
138+
139+
// Clear them so GetErrorData() doesn't render them again.
140+
m_diagnostics.clear();
141+
return m_diag_stream.GetString();
142+
}
143+
144+
llvm::StringRef CommandReturnObject::GetErrorData() {
145+
// Diagnostics haven't been fetched; render them now (not inlined).
146+
if (!m_diagnostics.empty()) {
147+
RenderDiagnosticDetails(GetErrorStream(), std::nullopt, false,
148+
m_diagnostics);
149+
m_diagnostics.clear();
150+
}
151+
152+
lldb::StreamSP stream_sp(m_err_stream.GetStreamAtIndex(eStreamStringIndex));
153+
if (stream_sp)
154+
return std::static_pointer_cast<StreamString>(stream_sp)->GetString();
155+
return llvm::StringRef();
117156
}
118157

119158
// Similar to AppendError, but do not prepend 'Status: ' to message, and don't

lldb/source/Utility/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ add_lldb_library(lldbUtility NO_INTERNAL_DEPENDENCIES
3838
DataEncoder.cpp
3939
DataExtractor.cpp
4040
Diagnostics.cpp
41+
DiagnosticsRendering.cpp
4142
Environment.cpp
4243
ErrorMessages.cpp
4344
Event.cpp

lldb/source/Commands/DiagnosticRendering.h renamed to lldb/source/Utility/DiagnosticsRendering.cpp

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
1-
//===-- DiagnosticRendering.h -----------------------------------*- C++ -*-===//
1+
//===-- DiagnosticsRendering.cpp ------------------------------------------===//
22
//
33
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
44
// See https://llvm.org/LICENSE.txt for license information.
55
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
66
//
77
//===----------------------------------------------------------------------===//
88

9-
#ifndef LLDB_SOURCE_COMMANDS_DIAGNOSTICRENDERING_H
10-
#define LLDB_SOURCE_COMMANDS_DIAGNOSTICRENDERING_H
9+
#include "lldb/Utility/DiagnosticsRendering.h"
1110

12-
#include "lldb/Expression/DiagnosticManager.h"
13-
#include "lldb/Utility/Stream.h"
14-
#include "llvm/Support/WithColor.h"
11+
using namespace lldb_private;
12+
using namespace lldb;
1513

1614
namespace lldb_private {
1715

18-
static llvm::raw_ostream &PrintSeverity(Stream &stream,
19-
lldb::Severity severity) {
16+
llvm::raw_ostream &PrintSeverity(Stream &stream, lldb::Severity severity) {
2017
llvm::HighlightColor color;
2118
llvm::StringRef text;
2219
switch (severity) {
@@ -36,12 +33,11 @@ static llvm::raw_ostream &PrintSeverity(Stream &stream,
3633
return llvm::WithColor(stream.AsRawOstream(), color, llvm::ColorMode::Enable)
3734
<< text;
3835
}
39-
40-
// Public for unittesting.
41-
static void RenderDiagnosticDetails(Stream &stream,
42-
std::optional<uint16_t> offset_in_command,
43-
bool show_inline,
44-
llvm::ArrayRef<DiagnosticDetail> details) {
36+
37+
void RenderDiagnosticDetails(Stream &stream,
38+
std::optional<uint16_t> offset_in_command,
39+
bool show_inline,
40+
llvm::ArrayRef<DiagnosticDetail> details) {
4541
if (details.empty())
4642
return;
4743

@@ -130,4 +126,3 @@ static void RenderDiagnosticDetails(Stream &stream,
130126
}
131127

132128
} // namespace lldb_private
133-
#endif
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# RUN: echo quit | %lldb -o "dwim-print a" \
2+
# RUN: | FileCheck %s --strict-whitespace --check-prefix=CHECK1
3+
# (lldb) dwim-print a
4+
# CHECK1:{{^ \^}}
5+
# CHECK1: {{^ ╰─ error: use of undeclared identifier 'a'}}
6+
# RUN: echo quit | %lldb -o "p a" \
7+
# RUN: | FileCheck %s --strict-whitespace --check-prefix=CHECK2
8+
# (lldb) p a
9+
# CHECK2:{{^ \^}}
10+
# RUN: echo quit | %lldb -o "dwim-print -- a" \
11+
# RUN: | FileCheck %s --strict-whitespace --check-prefix=CHECK3
12+
# (lldb) dwim-print -- a
13+
# CHECK3:{{^ \^}}
14+
# RUN: echo quit | %lldb -o "settings set show-inline-diagnostics false" \
15+
# RUN: -o "dwim-print a" 2>&1 | FileCheck %s --check-prefix=CHECK4
16+
# CHECK4: error: <user expression 0>:1:1: use of undeclared identifier

lldb/unittests/Interpreter/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
add_lldb_unittest(InterpreterTests
22
TestCommandPaths.cpp
3-
TestCommandObjectExpression.cpp
43
TestCompletion.cpp
54
TestOptionArgParser.cpp
65
TestOptions.cpp

0 commit comments

Comments
 (0)