Skip to content

Commit a2fc9d8

Browse files
DanTupCommit Queue
authored andcommitted
[analyzer] Improve locations for diagnostic context messages when no names
Previously -1 was used as the offset for unnamed elements when building diagnostics, but this is never a valid location (and for VS Code, results in the diagnostic not appearing at all, even if the invalid location is only in a context message). With this change we use the offset of the fragment instead. Fixes #62955. Change-Id: I6aec9b7a9a6d49b151b1cf3eaea4b684486b6d8d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/490121 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
1 parent 3369dff commit a2fc9d8

5 files changed

Lines changed: 96 additions & 9 deletions

File tree

pkg/analysis_server/test/lsp/diagnostic_test.dart

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,37 @@ void f() {
210210
expect(diagnostic.relatedInformation, hasLength(1));
211211
}
212212

213+
Future<void> test_contextMessage_unnamedElement_issue62955() async {
214+
var code = TestCode.parseNormalized('''
215+
void main() {
216+
[].named;
217+
}
218+
219+
/*[0*//*0]*/extension<T> on List<T> {
220+
Iterable<T> get named => throw 0;
221+
}
222+
223+
/*[1*//*1]*/extension<T> on List<T> {
224+
T? named(String? name) => throw 0;
225+
}
226+
''');
227+
newFile(mainFilePath, code.code);
228+
229+
var diagnosticsUpdate = waitForDiagnostics(mainFileUri);
230+
await initialize();
231+
var diagnostics = await diagnosticsUpdate;
232+
var diagnostic = diagnostics!.singleWhere(
233+
(diagnostic) => diagnostic.code == 'ambiguous_extension_member_access',
234+
);
235+
236+
expect(diagnostic.relatedInformation, hasLength(2));
237+
var [related1, related2] = diagnostic.relatedInformation!;
238+
expect(related1.message, contains('<unnamed extension> is defined in'));
239+
expect(related1.location.range, code.ranges[0].range);
240+
expect(related2.message, contains('<unnamed extension> is defined in'));
241+
expect(related2.location.range, code.ranges[1].range);
242+
}
243+
213244
Future<void> test_correction() async {
214245
newFile(mainFilePath, '''
215246
void f() {

pkg/analyzer/lib/src/dart/element/element.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1767,6 +1767,7 @@ abstract class ElementImpl implements Element {
17671767
libraryFragment: _firstFragment.libraryFragment,
17681768
name: _firstFragment.name,
17691769
nameOffset: _firstFragment.nameOffset,
1770+
firstTokenOffset: _firstFragment.firstTokenOffset,
17701771
);
17711772
}
17721773

@@ -3131,11 +3132,13 @@ class FirstFragmentLocation {
31313132
final LibraryFragmentImpl? libraryFragment;
31323133
final String? name;
31333134
final int? nameOffset;
3135+
final int? firstTokenOffset;
31343136

31353137
FirstFragmentLocation({
31363138
required this.libraryFragment,
31373139
required this.name,
31383140
required this.nameOffset,
3141+
required this.firstTokenOffset,
31393142
});
31403143
}
31413144

pkg/analyzer/lib/src/error/listener.dart

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import 'package:analyzer/source/source.dart';
1414
import 'package:analyzer/source/source_range.dart';
1515
import 'package:analyzer/src/dart/ast/ast.dart';
1616
import 'package:analyzer/src/dart/ast/extensions.dart';
17+
import 'package:analyzer/src/dart/element/element.dart';
1718
import 'package:analyzer/src/dart/element/extensions.dart';
1819
import 'package:analyzer/src/dart/element/type.dart';
1920
import 'package:analyzer/src/diagnostic/diagnostic_message.dart';
@@ -80,9 +81,12 @@ List<DiagnosticMessage> convertTypeNames(
8081
// context messages, remove the extra text added to the buffer.
8182
StringBuffer? buffer;
8283
for (var element in typeToConvert.allElements) {
83-
var name = element.name;
84-
name ??= element is ExtensionElement ? unnamedExtension : unnamed;
85-
var sourcePath = element.firstFragment.libraryFragment!.source.fullName;
84+
var nonSynthetic = element.nonSynthetic;
85+
var name = nonSynthetic.name;
86+
name ??= nonSynthetic is ExtensionElement ? unnamedExtension : unnamed;
87+
88+
var location = _DiagnosticLocation.forElement(nonSynthetic);
89+
var sourcePath = location.sourcePath!;
8690
if (nameToElementMap[name]!.length > 1) {
8791
if (buffer == null) {
8892
buffer = StringBuffer();
@@ -95,9 +99,9 @@ List<DiagnosticMessage> convertTypeNames(
9599
messages.add(
96100
DiagnosticMessageImpl(
97101
filePath: sourcePath,
98-
length: element.name?.length ?? 0,
99102
message: '$name is defined in $sourcePath',
100-
offset: element.firstFragment.nameOffset ?? -1,
103+
offset: location.offset,
104+
length: location.length,
101105
url: null,
102106
),
103107
);
@@ -203,10 +207,11 @@ class DiagnosticReporter {
203207
List<DiagnosticMessage>? contextMessages,
204208
}) {
205209
var nonSynthetic = element.nonSynthetic;
210+
var location = _DiagnosticLocation.forElement(nonSynthetic);
206211
return atOffset(
207212
diagnosticCode: diagnosticCode,
208-
offset: nonSynthetic.firstFragment.nameOffset ?? -1,
209-
length: nonSynthetic.name?.length ?? 0,
213+
offset: location.offset,
214+
length: location.length,
210215
arguments: arguments,
211216
contextMessages: contextMessages,
212217
);
@@ -391,6 +396,49 @@ class DiagnosticReporter {
391396
}
392397
}
393398

399+
/// The location to report a diagnostic at.
400+
class _DiagnosticLocation {
401+
final int offset;
402+
final int length;
403+
final String? sourcePath;
404+
405+
_DiagnosticLocation(
406+
this.sourcePath, {
407+
required this.offset,
408+
required this.length,
409+
});
410+
411+
/// Computes the diagnostic location for a given element.
412+
factory _DiagnosticLocation.forElement(Element element) {
413+
var location = (element.nonSynthetic as ElementImpl).firstFragmentLocation;
414+
415+
var sourcePath = location.libraryFragment?.source.fullName;
416+
var nameOffset = location.nameOffset;
417+
var firstTokenOffset = location.firstTokenOffset;
418+
419+
int offset, length;
420+
if (nameOffset != null) {
421+
// Has name, use offset and length.
422+
offset = nameOffset;
423+
length = location.name?.length ?? 0;
424+
} else if (firstTokenOffset != null) {
425+
// No name, use zero-length offset at the first token,
426+
offset = firstTokenOffset;
427+
length = 0;
428+
} else {
429+
// We don't have any valid location to use. We currently use -1 as an
430+
// indicator of this. We could consider using 0 (so the location is at
431+
// least valid, even if not specific), but it might make it more difficult
432+
// to track down bugs (for example the LSP mapping code has an assert that
433+
// would flag this if it happened during a test/assert-enabled run).
434+
offset = -1;
435+
length = 0;
436+
}
437+
438+
return _DiagnosticLocation(sourcePath, offset: offset, length: length);
439+
}
440+
}
441+
394442
/// Used by [convertTypeNames] to keep track of an error argument that is an
395443
/// [Element], that is being converted to a display string.
396444
class _ElementToConvert implements _ToConvert {

pkg/analyzer/test/error/error_reporter_test.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class ErrorReporterTest extends PubPackageResolutionTest {
3838

3939
test_atElement_unnamed() async {
4040
await resolveTestCode(r'''
41+
// comment to prevent expected offset being 0
4142
extension on int {}
4243
''');
4344
var element = findElement2.unnamedExtension();
@@ -50,7 +51,8 @@ extension on int {}
5051
reporter.atElement2(element, diag.castToNonType, arguments: ['A']);
5152

5253
var diagnostic = listener.diagnostics[0];
53-
expect(diagnostic.offset, -1);
54+
// No name, so expect offset of declaration.
55+
expect(diagnostic.offset, firstFragment.offset);
5456
}
5557

5658
test_atNode_types_differentNames() async {

pkg/analyzer/test/src/diagnostics/ambiguous_extension_member_access_test.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,10 @@ extension on Iterable<Undef2> { void foo() {} }
238238
"'extension on Iterable<InvalidType> "
239239
"(where <unnamed extension> is defined in ${testFile.path})',",
240240
],
241-
contextMessages: [message(testFile, -1, 0), message(testFile, -1, 0)],
241+
contextMessages: [
242+
message(testFile, 75, 0),
243+
message(testFile, 123, 0),
244+
],
242245
),
243246
error(diag.nonTypeAsTypeArgument, 97, 6),
244247
error(diag.nonTypeAsTypeArgument, 145, 6),

0 commit comments

Comments
 (0)