Skip to content

Commit f9bf821

Browse files
DanTupCommit Queue
authored andcommitted
[analysis_server] Don't include expressions/getters in inline values for the current line
For convenience, we usually include Inline Values for the "current" line so that when stopped at an `if` statement, you can see their values: ``` if (foo == 1) { // [foo = 2] ``` We have an experiment that includes inline values for getters (since although they can have side-effects, users tend to expect to see them in debug views, especially when the getter/setter are implicit), but triggering them before the code ever would have may be more unexpected. This change uses two different ranges so that only variables are included for the current line. Change-Id: I19a129a28f7b5296281dbee139855c738cd37194 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/418340 Commit-Queue: Phil Quitslund <[email protected]> Commit-Queue: Brian Wilkerson <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]> Reviewed-by: Phil Quitslund <[email protected]>
1 parent 35f84de commit f9bf821

File tree

2 files changed

+57
-13
lines changed

2 files changed

+57
-13
lines changed

pkg/analysis_server/lib/src/lsp/handlers/handler_inline_value.dart

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,19 @@ class InlineValueHandler
5353
}
5454
var lineInfo = unitResult.lineInfo;
5555

56-
// We will provide values from the start of the visible range up to
57-
// the end of the line the debugger is stopped on (which will do by just
58-
// jumping to position 0 of the next line).
56+
// Compute the ranges for which we will provide values. We produce two
57+
// ranges here because for some kinds of variables (simple values) it's
58+
// convenient to see them on an `if` statement on the same line that
59+
// hasn't executed yet. However, we should avoid evaluating getters which
60+
// may have side effects if they haven't executed previously, because this
61+
// may change state in a way that's less obvious.
5962
var visibleRange = params.range;
6063
var stoppedLocation = params.context.stoppedLocation;
61-
var applicableRange = Range(
64+
var rangeAlreadyExecuted = Range(
65+
start: visibleRange.start,
66+
end: stoppedLocation.end,
67+
);
68+
var rangeIncludingCurrentLine = Range(
6269
start: visibleRange.start,
6370
end: Position(line: stoppedLocation.end.line + 1, character: 0),
6471
);
@@ -75,7 +82,11 @@ class InlineValueHandler
7582
return success(null);
7683
}
7784

78-
var collector = _InlineValueCollector(lineInfo, applicableRange);
85+
var collector = _InlineValueCollector(
86+
lineInfo,
87+
rangeAlreadyExecuted: rangeAlreadyExecuted,
88+
rangeIncludingCurrentLine: rangeIncludingCurrentLine,
89+
);
7990
var visitor = _InlineValueVisitor(
8091
server.lspClientConfiguration,
8192
collector,
@@ -113,17 +124,28 @@ class _InlineValueCollector {
113124
/// A map of elements and their inline value.
114125
final Map<Element2, InlineValue> values = {};
115126

116-
/// The range for which inline values should be retained.
127+
/// The range for which simple inline values should be returned.
128+
///
129+
/// This should be approximately the range of the visible code on screen up to
130+
/// the point of execution and including the current line.
131+
final Range rangeIncludingCurrentLine;
132+
133+
/// The range for which complex inline values (such as getters) should be
134+
/// returned.
117135
///
118136
/// This should be approximately the range of the visible code on screen up to
119137
/// the point of execution.
120-
final Range applicableRange;
138+
final Range rangeAlreadyExecuted;
121139

122140
/// A [LineInfo] used to convert offsets to lines/columns for comparing to
123141
/// [applicableRange].
124142
final LineInfo lineInfo;
125143

126-
_InlineValueCollector(this.lineInfo, this.applicableRange);
144+
_InlineValueCollector(
145+
this.lineInfo, {
146+
required this.rangeAlreadyExecuted,
147+
required this.rangeIncludingCurrentLine,
148+
});
127149

128150
/// Records an expression inline value for [element] with [offset]/[length].
129151
///
@@ -136,6 +158,11 @@ class _InlineValueCollector {
136158

137159
var range = toRange(lineInfo, offset, length);
138160

161+
// Don't record anything outside of the visible range (excluding next line).
162+
if (!range.intersects(rangeAlreadyExecuted)) {
163+
return;
164+
}
165+
139166
var value = InlineValue.t1(
140167
InlineValueEvaluatableExpression(
141168
range: range,
@@ -158,6 +185,11 @@ class _InlineValueCollector {
158185

159186
var range = toRange(lineInfo, offset, length);
160187

188+
// Don't record anything outside of the visible range (including next line).
189+
if (!range.intersects(rangeIncludingCurrentLine)) {
190+
return;
191+
}
192+
161193
var value = InlineValue.t3(
162194
InlineValueVariableLookup(
163195
caseSensitiveLookup: true,
@@ -183,11 +215,6 @@ class _InlineValueCollector {
183215
void _record(InlineValue value, Element2 element) {
184216
var range = _getRange(value);
185217

186-
// Never record anything outside of the visible range.
187-
if (!range.intersects(applicableRange)) {
188-
return;
189-
}
190-
191218
// We only want to show each variable once, so keep only the one furthest
192219
// into the source (closest to the execution pointer).
193220
if (values[element] case var existingValue?) {

pkg/analysis_server/test/lsp/inline_value_test.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,23 @@ void f(A /*[0*/a/*0]*/, int b) {
193193
await verify_values(code, ofType: InlineValueVariableLookup);
194194
}
195195

196+
/// Unlike variables, which we include for the line of the execution pointer
197+
/// (to aid with reviewing conditional statements), getters are only evaluated
198+
/// if they are before the execution pointer to reduce the chance of
199+
/// triggering side-effects before the code would have.
200+
Future<void> test_property_range_onlyBeforePointer() async {
201+
experimentalInlineValuesProperties = true;
202+
203+
code = TestCode.parse(r'''
204+
void f(String /*[0*/s/*0]*/) {
205+
^if (s.isNotEmpty) {
206+
}
207+
}
208+
''');
209+
210+
await verify_values(code, ofType: InlineValueVariableLookup);
211+
}
212+
196213
Future<void> test_property_setter() async {
197214
experimentalInlineValuesProperties = true;
198215

0 commit comments

Comments
 (0)