Skip to content

Commit 9baaa5f

Browse files
srawlinsCommit Queue
authored andcommitted
Reland "analyzer: Write out qualified extension names in error messages"
This is a reland of commit 10c1d88 This includes a fix in listener.dart to not null-assert on an element's name (`element.name!`). This fixes a problem in google internal code. Original change's description: > 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]> Change-Id: I6c20cf023af26b2d3b0fe9d8193f5e067719da8a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/389587 Reviewed-by: Konstantin Shcheglov <[email protected]> Commit-Queue: Samuel Rawlins <[email protected]>
1 parent 7023392 commit 9baaa5f

File tree

10 files changed

+297
-147
lines changed

10 files changed

+297
-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
@@ -748,7 +748,10 @@ final _builtInLintProducers = <LintCode, List<ProducerGenerator>>{
748748
};
749749

750750
final _builtInNonLintMultiProducers = {
751-
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS: [
751+
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO: [
752+
AddExtensionOverride.new,
753+
],
754+
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_THREE_OR_MORE: [
752755
AddExtensionOverride.new,
753756
],
754757
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: 140 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,82 @@ 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+
const unnamedExtension = '<unnamed extension>';
386+
const unnamed = '<unnamed>';
387+
var nameToElementMap = <String, Set<Element>>{};
388+
for (var typeToConvert in typeGroup) {
389+
for (var element in typeToConvert.allElements) {
390+
var name = element.name;
391+
name ??= element is ExtensionElement ? unnamedExtension : unnamed;
392+
393+
var elements = nameToElementMap.putIfAbsent(name, () => {});
394+
elements.add(element);
392395
}
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-
}
396+
}
415397

416-
if (buffer != null) {
417-
arguments[typeToConvert.index] =
418-
'${typeToConvert.displayName} ($buffer)';
419-
} else {
420-
arguments[typeToConvert.index] = typeToConvert.displayName;
398+
for (var typeToConvert in typeGroup) {
399+
// TODO(brianwilkerson): When clients do a better job of displaying
400+
// context messages, remove the extra text added to the buffer.
401+
StringBuffer? buffer;
402+
for (var element in typeToConvert.allElements) {
403+
var name = element.name;
404+
name ??= element is ExtensionElement ? unnamedExtension : unnamed;
405+
var sourcePath = element.source!.fullName;
406+
if (nameToElementMap[name]!.length > 1) {
407+
if (buffer == null) {
408+
buffer = StringBuffer();
409+
buffer.write('where ');
410+
} else {
411+
buffer.write(', ');
412+
}
413+
buffer.write('$name is defined in $sourcePath');
421414
}
415+
messages.add(DiagnosticMessageImpl(
416+
filePath: element.source!.fullName,
417+
length: element.nameLength,
418+
message: '$name is defined in $sourcePath',
419+
offset: element.nameOffset,
420+
url: null,
421+
));
422422
}
423+
424+
arguments[typeToConvert.index] = buffer != null
425+
? '${typeToConvert.displayName} ($buffer)'
426+
: typeToConvert.displayName;
423427
}
424428
}
425429
return messages;
@@ -453,6 +457,22 @@ class RecordingErrorListener implements AnalysisErrorListener {
453457
}
454458
}
455459

460+
/// Used by [ErrorReporter._convertTypeNames] to keep track of an error argument
461+
/// that is an [Element], that is being converted to a display string.
462+
class _ElementToConvert implements _ToConvert {
463+
@override
464+
final int index;
465+
466+
@override
467+
final String displayName;
468+
469+
@override
470+
final Iterable<Element> allElements;
471+
472+
_ElementToConvert(this.index, Element element, this.displayName)
473+
: allElements = [element];
474+
}
475+
456476
/// An [AnalysisErrorListener] that ignores error.
457477
class _NullErrorListener implements AnalysisErrorListener {
458478
@override
@@ -461,42 +481,61 @@ class _NullErrorListener implements AnalysisErrorListener {
461481
}
462482
}
463483

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;
484+
/// Used by [ErrorReporter._convertTypeNames] to keep track of an argument that
485+
/// is being converted to a display string.
486+
abstract class _ToConvert {
487+
/// A list of all elements involved in the [DartType] or [Element]'s display
488+
/// string.
489+
Iterable<Element> get allElements;
470490

471-
List<Element>? _allElements;
491+
/// The argument's display string, to replace the argument in the argument
492+
/// list.
493+
String get displayName;
472494

473-
_TypeToConvert(this.index, this.type, this.displayName);
495+
/// The index of the argument in the argument list.
496+
int get index;
497+
}
474498

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

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-
}
505+
final DartType _type;
506+
507+
@override
508+
final String displayName;
509+
510+
@override
511+
late final Iterable<Element> allElements = () {
512+
var elements = <Element>{};
513+
514+
void addElementsFrom(DartType type) {
515+
if (type is FunctionType) {
516+
addElementsFrom(type.returnType);
517+
for (var parameter in type.parameters) {
518+
addElementsFrom(parameter.type);
519+
}
520+
} else if (type is RecordType) {
521+
for (var parameter in type.fields) {
522+
addElementsFrom(parameter.type);
523+
}
524+
} else if (type is InterfaceType) {
525+
if (elements.add(type.element)) {
526+
for (var typeArgument in type.typeArguments) {
527+
addElementsFrom(typeArgument);
490528
}
491529
}
492530
}
493-
494-
addElementsFrom(type);
495-
_allElements = elements.where((element) {
496-
var name = element.name;
497-
return name != null && name.isNotEmpty;
498-
}).toList();
499531
}
500-
return _allElements!;
501-
}
532+
533+
addElementsFrom(_type);
534+
return elements.where((element) {
535+
var name = element.name;
536+
return name != null && name.isNotEmpty;
537+
});
538+
}();
539+
540+
_TypeToConvert(this.index, this._type, this.displayName);
502541
}

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)