Skip to content

Commit 10c1d88

Browse files
srawlinsCommit Queue
authored andcommitted
analyzer: Write out qualified extension names in error messages
Fixes #56269 Change-Id: I025966fd4aa3d7c5b71175321f21f95e8c41f086 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/388580 Reviewed-by: Konstantin Shcheglov <[email protected]> Commit-Queue: Samuel Rawlins <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]>
1 parent 07fd587 commit 10c1d88

File tree

10 files changed

+265
-147
lines changed

10 files changed

+265
-147
lines changed

pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,9 @@ CompileTimeErrorCode.AMBIGUOUS_EXPORT:
151151
status: needsFix
152152
notes: |-
153153
For each exported name, add a fix to hide the name.
154-
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS:
154+
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_THREE_OR_MORE:
155+
status: hasFix
156+
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO:
155157
status: hasFix
156158
CompileTimeErrorCode.AMBIGUOUS_IMPORT:
157159
status: needsFix

pkg/analysis_server/lib/src/services/correction/fix_internal.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -747,7 +747,10 @@ final _builtInLintProducers = <LintCode, List<ProducerGenerator>>{
747747
};
748748

749749
final _builtInNonLintMultiProducers = {
750-
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS: [
750+
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO: [
751+
AddExtensionOverride.new,
752+
],
753+
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_THREE_OR_MORE: [
751754
AddExtensionOverride.new,
752755
],
753756
CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE: [

pkg/analysis_server/test/src/services/correction/fix/add_extension_override_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ f() {
4444
}
4545
''', expectedNumberOfFixesForKind: 1, errorFilter: (error) {
4646
return error.errorCode ==
47-
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS;
47+
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO;
4848
});
4949
}
5050

pkg/analyzer/lib/error/listener.dart

Lines changed: 134 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import 'package:analyzer/dart/element/type.dart';
1111
import 'package:analyzer/diagnostic/diagnostic.dart';
1212
import 'package:analyzer/error/error.dart';
1313
import 'package:analyzer/source/source.dart';
14+
import 'package:analyzer/src/dart/element/extensions.dart';
1415
import 'package:analyzer/src/dart/element/type.dart';
1516
import 'package:analyzer/src/diagnostic/diagnostic.dart';
17+
import 'package:analyzer/src/utilities/extensions/collection.dart';
1618
import 'package:meta/meta.dart';
1719
import 'package:source_span/source_span.dart';
1820

@@ -168,7 +170,19 @@ class ErrorReporter {
168170
return;
169171
}
170172

171-
_convertElements(arguments);
173+
if (arguments != null) {
174+
var invalid = arguments
175+
.whereNotType<String>()
176+
.whereNotType<DartType>()
177+
.whereNotType<Element>()
178+
.whereNotType<int>()
179+
.whereNotType<Uri>();
180+
if (invalid.isNotEmpty) {
181+
throw ArgumentError('Tried to format an error using '
182+
'${invalid.map((e) => e.runtimeType).join(', ')}');
183+
}
184+
}
185+
172186
contextMessages ??= [];
173187
contextMessages.addAll(_convertTypeNames(arguments));
174188
_errorListener.onError(
@@ -334,92 +348,76 @@ class ErrorReporter {
334348
);
335349
}
336350

337-
/// Convert all [Element]s in the [arguments] into their display strings.
338-
void _convertElements(List<Object>? arguments) {
339-
if (arguments == null) {
340-
return;
341-
}
342-
343-
for (var i = 0; i < arguments.length; i++) {
344-
var argument = arguments[i];
345-
if (argument is Element) {
346-
arguments[i] = argument.getDisplayString();
347-
} else if (!(argument is String ||
348-
argument is DartType ||
349-
argument is int ||
350-
argument is Uri)) {
351-
throw ArgumentError(
352-
'Tried to format an error using ${argument.runtimeType}');
353-
}
354-
}
355-
}
356-
357-
/// Given an array of [arguments] that is expected to contain two or more
358-
/// types, convert the types into strings by using the display names of the
359-
/// types, unless there are two or more types with the same names, in which
360-
/// case the extended display names of the types will be used in order to
351+
/// Given an array of [arguments] that may contain [DartType]s and [Element]s,
352+
/// converts the types and elements into strings by using the display names of
353+
/// each, unless there are two or more types or elements with the same display
354+
/// names, in which case the extended display names will be used in order to
361355
/// clarify the message.
362356
List<DiagnosticMessage> _convertTypeNames(List<Object?>? arguments) {
363-
var messages = <DiagnosticMessage>[];
364357
if (arguments == null) {
365-
return messages;
358+
return const [];
366359
}
367360

368-
Map<String, List<_TypeToConvert>> typeGroups = {};
369-
for (int i = 0; i < arguments.length; i++) {
361+
var typeGroups = <String, List<_ToConvert>>{};
362+
for (var i = 0; i < arguments.length; i++) {
370363
var argument = arguments[i];
371364
if (argument is TypeImpl) {
372-
String displayName = argument.getDisplayString(
373-
preferTypeAlias: true,
374-
);
375-
List<_TypeToConvert> types =
376-
typeGroups.putIfAbsent(displayName, () => <_TypeToConvert>[]);
365+
var displayName = argument.getDisplayString(preferTypeAlias: true);
366+
var types = typeGroups.putIfAbsent(displayName, () => []);
377367
types.add(_TypeToConvert(i, argument, displayName));
368+
} else if (argument is Element) {
369+
var displayName = argument.getDisplayString();
370+
var types = typeGroups.putIfAbsent(displayName, () => []);
371+
types.add(_ElementToConvert(i, argument, displayName));
378372
}
379373
}
380-
for (List<_TypeToConvert> typeGroup in typeGroups.values) {
374+
375+
var messages = <DiagnosticMessage>[];
376+
for (var typeGroup in typeGroups.values) {
381377
if (typeGroup.length == 1) {
382-
_TypeToConvert typeToConvert = typeGroup[0];
378+
var typeToConvert = typeGroup[0];
379+
// If the display name of a type is unambiguous, just replace the type
380+
// in the arguments list with its display name.
383381
arguments[typeToConvert.index] = typeToConvert.displayName;
384-
} else {
385-
Map<String, Set<Element>> nameToElementMap = {};
386-
for (_TypeToConvert typeToConvert in typeGroup) {
387-
for (Element element in typeToConvert.allElements()) {
388-
Set<Element> elements =
389-
nameToElementMap.putIfAbsent(element.name!, () => <Element>{});
390-
elements.add(element);
391-
}
382+
continue;
383+
}
384+
385+
var nameToElementMap = <String, Set<Element>>{};
386+
for (var typeToConvert in typeGroup) {
387+
for (var element in typeToConvert.allElements) {
388+
var elements = nameToElementMap.putIfAbsent(element.name!, () => {});
389+
elements.add(element);
392390
}
393-
for (_TypeToConvert typeToConvert in typeGroup) {
394-
// TODO(brianwilkerson): When clients do a better job of displaying
395-
// context messages, remove the extra text added to the buffer.
396-
StringBuffer? buffer;
397-
for (Element element in typeToConvert.allElements()) {
398-
String name = element.name!;
399-
if (nameToElementMap[name]!.length > 1) {
400-
if (buffer == null) {
401-
buffer = StringBuffer();
402-
buffer.write('where ');
403-
} else {
404-
buffer.write(', ');
405-
}
406-
buffer.write('$name is defined in ${element.source!.fullName}');
407-
}
408-
messages.add(DiagnosticMessageImpl(
409-
filePath: element.source!.fullName,
410-
length: element.nameLength,
411-
message: '$name is defined in ${element.source!.fullName}',
412-
offset: element.nameOffset,
413-
url: null));
414-
}
391+
}
415392

416-
if (buffer != null) {
417-
arguments[typeToConvert.index] =
418-
'${typeToConvert.displayName} ($buffer)';
419-
} else {
420-
arguments[typeToConvert.index] = typeToConvert.displayName;
393+
for (var typeToConvert in typeGroup) {
394+
// TODO(brianwilkerson): When clients do a better job of displaying
395+
// context messages, remove the extra text added to the buffer.
396+
StringBuffer? buffer;
397+
for (var element in typeToConvert.allElements) {
398+
var name = element.name!;
399+
var sourcePath = element.source!.fullName;
400+
if (nameToElementMap[name]!.length > 1) {
401+
if (buffer == null) {
402+
buffer = StringBuffer();
403+
buffer.write('where ');
404+
} else {
405+
buffer.write(', ');
406+
}
407+
buffer.write('$name is defined in $sourcePath');
421408
}
409+
messages.add(DiagnosticMessageImpl(
410+
filePath: element.source!.fullName,
411+
length: element.nameLength,
412+
message: '$name is defined in $sourcePath',
413+
offset: element.nameOffset,
414+
url: null,
415+
));
422416
}
417+
418+
arguments[typeToConvert.index] = buffer != null
419+
? '${typeToConvert.displayName} ($buffer)'
420+
: typeToConvert.displayName;
423421
}
424422
}
425423
return messages;
@@ -453,6 +451,22 @@ class RecordingErrorListener implements AnalysisErrorListener {
453451
}
454452
}
455453

454+
/// Used by [ErrorReporter._convertTypeNames] to keep track of an error argument
455+
/// that is an [Element], that is being converted to a display string.
456+
class _ElementToConvert implements _ToConvert {
457+
@override
458+
final int index;
459+
460+
@override
461+
final String displayName;
462+
463+
@override
464+
final Iterable<Element> allElements;
465+
466+
_ElementToConvert(this.index, Element element, this.displayName)
467+
: allElements = [element];
468+
}
469+
456470
/// An [AnalysisErrorListener] that ignores error.
457471
class _NullErrorListener implements AnalysisErrorListener {
458472
@override
@@ -461,42 +475,61 @@ class _NullErrorListener implements AnalysisErrorListener {
461475
}
462476
}
463477

464-
/// Used by `ErrorReporter._convertTypeNames` to keep track of a type that is
465-
/// being converted.
466-
class _TypeToConvert {
467-
final int index;
468-
final DartType type;
469-
final String displayName;
478+
/// Used by [ErrorReporter._convertTypeNames] to keep track of an argument that
479+
/// is being converted to a display string.
480+
abstract class _ToConvert {
481+
/// A list of all elements involved in the [DartType] or [Element]'s display
482+
/// string.
483+
Iterable<Element> get allElements;
470484

471-
List<Element>? _allElements;
485+
/// The argument's display string, to replace the argument in the argument
486+
/// list.
487+
String get displayName;
472488

473-
_TypeToConvert(this.index, this.type, this.displayName);
489+
/// The index of the argument in the argument list.
490+
int get index;
491+
}
474492

475-
List<Element> allElements() {
476-
if (_allElements == null) {
477-
Set<Element> elements = <Element>{};
493+
/// Used by [ErrorReporter._convertTypeNames] to keep track of an error argument
494+
/// that is a [DartType], that is being converted to a display string.
495+
class _TypeToConvert implements _ToConvert {
496+
@override
497+
final int index;
478498

479-
void addElementsFrom(DartType type) {
480-
if (type is FunctionType) {
481-
addElementsFrom(type.returnType);
482-
for (ParameterElement parameter in type.parameters) {
483-
addElementsFrom(parameter.type);
484-
}
485-
} else if (type is InterfaceType) {
486-
if (elements.add(type.element)) {
487-
for (DartType typeArgument in type.typeArguments) {
488-
addElementsFrom(typeArgument);
489-
}
499+
final DartType _type;
500+
501+
@override
502+
final String displayName;
503+
504+
@override
505+
late final Iterable<Element> allElements = () {
506+
var elements = <Element>{};
507+
508+
void addElementsFrom(DartType type) {
509+
if (type is FunctionType) {
510+
addElementsFrom(type.returnType);
511+
for (var parameter in type.parameters) {
512+
addElementsFrom(parameter.type);
513+
}
514+
} else if (type is RecordType) {
515+
for (var parameter in type.fields) {
516+
addElementsFrom(parameter.type);
517+
}
518+
} else if (type is InterfaceType) {
519+
if (elements.add(type.element)) {
520+
for (var typeArgument in type.typeArguments) {
521+
addElementsFrom(typeArgument);
490522
}
491523
}
492524
}
493-
494-
addElementsFrom(type);
495-
_allElements = elements.where((element) {
496-
var name = element.name;
497-
return name != null && name.isNotEmpty;
498-
}).toList();
499525
}
500-
return _allElements!;
501-
}
526+
527+
addElementsFrom(_type);
528+
return elements.where((element) {
529+
var name = element.name;
530+
return name != null && name.isNotEmpty;
531+
});
532+
}();
533+
534+
_TypeToConvert(this.index, this._type, this.displayName);
502535
}

pkg/analyzer/lib/src/dart/resolver/extension_member_resolver.dart

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -117,21 +117,33 @@ class ExtensionMemberResolver {
117117
}
118118

119119
// The most specific extension is ambiguous.
120-
_errorReporter.atEntity(
121-
nameEntity,
122-
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS,
123-
arguments: [
124-
name.name,
125-
mostSpecific.map((e) {
126-
var name = e.extension.name;
127-
if (name != null) {
128-
return "extension '$name'";
129-
}
130-
var type = e.extension.extendedType.getDisplayString();
131-
return "unnamed extension on '$type'";
132-
}).commaSeparatedWithAnd,
133-
],
134-
);
120+
if (mostSpecific.length == 2) {
121+
_errorReporter.atEntity(
122+
nameEntity,
123+
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO,
124+
arguments: [
125+
name.name,
126+
mostSpecific[0].extension,
127+
mostSpecific[1].extension,
128+
],
129+
);
130+
} else {
131+
_errorReporter.atEntity(
132+
nameEntity,
133+
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_THREE_OR_MORE,
134+
arguments: [
135+
name.name,
136+
mostSpecific.map((e) {
137+
var name = e.extension.name;
138+
if (name != null) {
139+
return "extension '$name'";
140+
}
141+
var type = e.extension.extendedType.getDisplayString();
142+
return "unnamed extension on '$type'";
143+
}).commaSeparatedWithAnd,
144+
],
145+
);
146+
}
135147
return ResolutionResult.ambiguous;
136148
}
137149

0 commit comments

Comments
 (0)