analyzer: Write out qualified extension names in error messages
Fixes https://github.com/dart-lang/sdk/issues/56269 Change-Id: I025966fd4aa3d7c5b71175321f21f95e8c41f086 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/388580 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Commit-Queue: Samuel Rawlins <srawlins@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
committed by
Commit Queue
parent
07fd58715c
commit
10c1d883df
@@ -151,7 +151,9 @@ CompileTimeErrorCode.AMBIGUOUS_EXPORT:
|
||||
status: needsFix
|
||||
notes: |-
|
||||
For each exported name, add a fix to hide the name.
|
||||
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS:
|
||||
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_THREE_OR_MORE:
|
||||
status: hasFix
|
||||
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO:
|
||||
status: hasFix
|
||||
CompileTimeErrorCode.AMBIGUOUS_IMPORT:
|
||||
status: needsFix
|
||||
|
||||
@@ -747,7 +747,10 @@ final _builtInLintProducers = <LintCode, List<ProducerGenerator>>{
|
||||
};
|
||||
|
||||
final _builtInNonLintMultiProducers = {
|
||||
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS: [
|
||||
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO: [
|
||||
AddExtensionOverride.new,
|
||||
],
|
||||
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_THREE_OR_MORE: [
|
||||
AddExtensionOverride.new,
|
||||
],
|
||||
CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE: [
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ f() {
|
||||
}
|
||||
''', expectedNumberOfFixesForKind: 1, errorFilter: (error) {
|
||||
return error.errorCode ==
|
||||
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS;
|
||||
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,10 @@ import 'package:analyzer/dart/element/type.dart';
|
||||
import 'package:analyzer/diagnostic/diagnostic.dart';
|
||||
import 'package:analyzer/error/error.dart';
|
||||
import 'package:analyzer/source/source.dart';
|
||||
import 'package:analyzer/src/dart/element/extensions.dart';
|
||||
import 'package:analyzer/src/dart/element/type.dart';
|
||||
import 'package:analyzer/src/diagnostic/diagnostic.dart';
|
||||
import 'package:analyzer/src/utilities/extensions/collection.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
@@ -168,7 +170,19 @@ class ErrorReporter {
|
||||
return;
|
||||
}
|
||||
|
||||
_convertElements(arguments);
|
||||
if (arguments != null) {
|
||||
var invalid = arguments
|
||||
.whereNotType<String>()
|
||||
.whereNotType<DartType>()
|
||||
.whereNotType<Element>()
|
||||
.whereNotType<int>()
|
||||
.whereNotType<Uri>();
|
||||
if (invalid.isNotEmpty) {
|
||||
throw ArgumentError('Tried to format an error using '
|
||||
'${invalid.map((e) => e.runtimeType).join(', ')}');
|
||||
}
|
||||
}
|
||||
|
||||
contextMessages ??= [];
|
||||
contextMessages.addAll(_convertTypeNames(arguments));
|
||||
_errorListener.onError(
|
||||
@@ -334,93 +348,77 @@ class ErrorReporter {
|
||||
);
|
||||
}
|
||||
|
||||
/// Convert all [Element]s in the [arguments] into their display strings.
|
||||
void _convertElements(List<Object>? arguments) {
|
||||
if (arguments == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
var argument = arguments[i];
|
||||
if (argument is Element) {
|
||||
arguments[i] = argument.getDisplayString();
|
||||
} else if (!(argument is String ||
|
||||
argument is DartType ||
|
||||
argument is int ||
|
||||
argument is Uri)) {
|
||||
throw ArgumentError(
|
||||
'Tried to format an error using ${argument.runtimeType}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Given an array of [arguments] that is expected to contain two or more
|
||||
/// types, convert the types into strings by using the display names of the
|
||||
/// types, unless there are two or more types with the same names, in which
|
||||
/// case the extended display names of the types will be used in order to
|
||||
/// Given an array of [arguments] that may contain [DartType]s and [Element]s,
|
||||
/// converts the types and elements into strings by using the display names of
|
||||
/// each, unless there are two or more types or elements with the same display
|
||||
/// names, in which case the extended display names will be used in order to
|
||||
/// clarify the message.
|
||||
List<DiagnosticMessage> _convertTypeNames(List<Object?>? arguments) {
|
||||
var messages = <DiagnosticMessage>[];
|
||||
if (arguments == null) {
|
||||
return messages;
|
||||
return const [];
|
||||
}
|
||||
|
||||
Map<String, List<_TypeToConvert>> typeGroups = {};
|
||||
for (int i = 0; i < arguments.length; i++) {
|
||||
var typeGroups = <String, List<_ToConvert>>{};
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
var argument = arguments[i];
|
||||
if (argument is TypeImpl) {
|
||||
String displayName = argument.getDisplayString(
|
||||
preferTypeAlias: true,
|
||||
);
|
||||
List<_TypeToConvert> types =
|
||||
typeGroups.putIfAbsent(displayName, () => <_TypeToConvert>[]);
|
||||
var displayName = argument.getDisplayString(preferTypeAlias: true);
|
||||
var types = typeGroups.putIfAbsent(displayName, () => []);
|
||||
types.add(_TypeToConvert(i, argument, displayName));
|
||||
} else if (argument is Element) {
|
||||
var displayName = argument.getDisplayString();
|
||||
var types = typeGroups.putIfAbsent(displayName, () => []);
|
||||
types.add(_ElementToConvert(i, argument, displayName));
|
||||
}
|
||||
}
|
||||
for (List<_TypeToConvert> typeGroup in typeGroups.values) {
|
||||
if (typeGroup.length == 1) {
|
||||
_TypeToConvert typeToConvert = typeGroup[0];
|
||||
arguments[typeToConvert.index] = typeToConvert.displayName;
|
||||
} else {
|
||||
Map<String, Set<Element>> nameToElementMap = {};
|
||||
for (_TypeToConvert typeToConvert in typeGroup) {
|
||||
for (Element element in typeToConvert.allElements()) {
|
||||
Set<Element> elements =
|
||||
nameToElementMap.putIfAbsent(element.name!, () => <Element>{});
|
||||
elements.add(element);
|
||||
}
|
||||
}
|
||||
for (_TypeToConvert typeToConvert in typeGroup) {
|
||||
// TODO(brianwilkerson): When clients do a better job of displaying
|
||||
// context messages, remove the extra text added to the buffer.
|
||||
StringBuffer? buffer;
|
||||
for (Element element in typeToConvert.allElements()) {
|
||||
String name = element.name!;
|
||||
if (nameToElementMap[name]!.length > 1) {
|
||||
if (buffer == null) {
|
||||
buffer = StringBuffer();
|
||||
buffer.write('where ');
|
||||
} else {
|
||||
buffer.write(', ');
|
||||
}
|
||||
buffer.write('$name is defined in ${element.source!.fullName}');
|
||||
}
|
||||
messages.add(DiagnosticMessageImpl(
|
||||
filePath: element.source!.fullName,
|
||||
length: element.nameLength,
|
||||
message: '$name is defined in ${element.source!.fullName}',
|
||||
offset: element.nameOffset,
|
||||
url: null));
|
||||
}
|
||||
|
||||
if (buffer != null) {
|
||||
arguments[typeToConvert.index] =
|
||||
'${typeToConvert.displayName} ($buffer)';
|
||||
} else {
|
||||
arguments[typeToConvert.index] = typeToConvert.displayName;
|
||||
}
|
||||
var messages = <DiagnosticMessage>[];
|
||||
for (var typeGroup in typeGroups.values) {
|
||||
if (typeGroup.length == 1) {
|
||||
var typeToConvert = typeGroup[0];
|
||||
// If the display name of a type is unambiguous, just replace the type
|
||||
// in the arguments list with its display name.
|
||||
arguments[typeToConvert.index] = typeToConvert.displayName;
|
||||
continue;
|
||||
}
|
||||
|
||||
var nameToElementMap = <String, Set<Element>>{};
|
||||
for (var typeToConvert in typeGroup) {
|
||||
for (var element in typeToConvert.allElements) {
|
||||
var elements = nameToElementMap.putIfAbsent(element.name!, () => {});
|
||||
elements.add(element);
|
||||
}
|
||||
}
|
||||
|
||||
for (var typeToConvert in typeGroup) {
|
||||
// TODO(brianwilkerson): When clients do a better job of displaying
|
||||
// context messages, remove the extra text added to the buffer.
|
||||
StringBuffer? buffer;
|
||||
for (var element in typeToConvert.allElements) {
|
||||
var name = element.name!;
|
||||
var sourcePath = element.source!.fullName;
|
||||
if (nameToElementMap[name]!.length > 1) {
|
||||
if (buffer == null) {
|
||||
buffer = StringBuffer();
|
||||
buffer.write('where ');
|
||||
} else {
|
||||
buffer.write(', ');
|
||||
}
|
||||
buffer.write('$name is defined in $sourcePath');
|
||||
}
|
||||
messages.add(DiagnosticMessageImpl(
|
||||
filePath: element.source!.fullName,
|
||||
length: element.nameLength,
|
||||
message: '$name is defined in $sourcePath',
|
||||
offset: element.nameOffset,
|
||||
url: null,
|
||||
));
|
||||
}
|
||||
|
||||
arguments[typeToConvert.index] = buffer != null
|
||||
? '${typeToConvert.displayName} ($buffer)'
|
||||
: typeToConvert.displayName;
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
@@ -453,6 +451,22 @@ class RecordingErrorListener implements AnalysisErrorListener {
|
||||
}
|
||||
}
|
||||
|
||||
/// Used by [ErrorReporter._convertTypeNames] to keep track of an error argument
|
||||
/// that is an [Element], that is being converted to a display string.
|
||||
class _ElementToConvert implements _ToConvert {
|
||||
@override
|
||||
final int index;
|
||||
|
||||
@override
|
||||
final String displayName;
|
||||
|
||||
@override
|
||||
final Iterable<Element> allElements;
|
||||
|
||||
_ElementToConvert(this.index, Element element, this.displayName)
|
||||
: allElements = [element];
|
||||
}
|
||||
|
||||
/// An [AnalysisErrorListener] that ignores error.
|
||||
class _NullErrorListener implements AnalysisErrorListener {
|
||||
@override
|
||||
@@ -461,42 +475,61 @@ class _NullErrorListener implements AnalysisErrorListener {
|
||||
}
|
||||
}
|
||||
|
||||
/// Used by `ErrorReporter._convertTypeNames` to keep track of a type that is
|
||||
/// being converted.
|
||||
class _TypeToConvert {
|
||||
/// Used by [ErrorReporter._convertTypeNames] to keep track of an argument that
|
||||
/// is being converted to a display string.
|
||||
abstract class _ToConvert {
|
||||
/// A list of all elements involved in the [DartType] or [Element]'s display
|
||||
/// string.
|
||||
Iterable<Element> get allElements;
|
||||
|
||||
/// The argument's display string, to replace the argument in the argument
|
||||
/// list.
|
||||
String get displayName;
|
||||
|
||||
/// The index of the argument in the argument list.
|
||||
int get index;
|
||||
}
|
||||
|
||||
/// Used by [ErrorReporter._convertTypeNames] to keep track of an error argument
|
||||
/// that is a [DartType], that is being converted to a display string.
|
||||
class _TypeToConvert implements _ToConvert {
|
||||
@override
|
||||
final int index;
|
||||
final DartType type;
|
||||
|
||||
final DartType _type;
|
||||
|
||||
@override
|
||||
final String displayName;
|
||||
|
||||
List<Element>? _allElements;
|
||||
@override
|
||||
late final Iterable<Element> allElements = () {
|
||||
var elements = <Element>{};
|
||||
|
||||
_TypeToConvert(this.index, this.type, this.displayName);
|
||||
|
||||
List<Element> allElements() {
|
||||
if (_allElements == null) {
|
||||
Set<Element> elements = <Element>{};
|
||||
|
||||
void addElementsFrom(DartType type) {
|
||||
if (type is FunctionType) {
|
||||
addElementsFrom(type.returnType);
|
||||
for (ParameterElement parameter in type.parameters) {
|
||||
addElementsFrom(parameter.type);
|
||||
}
|
||||
} else if (type is InterfaceType) {
|
||||
if (elements.add(type.element)) {
|
||||
for (DartType typeArgument in type.typeArguments) {
|
||||
addElementsFrom(typeArgument);
|
||||
}
|
||||
void addElementsFrom(DartType type) {
|
||||
if (type is FunctionType) {
|
||||
addElementsFrom(type.returnType);
|
||||
for (var parameter in type.parameters) {
|
||||
addElementsFrom(parameter.type);
|
||||
}
|
||||
} else if (type is RecordType) {
|
||||
for (var parameter in type.fields) {
|
||||
addElementsFrom(parameter.type);
|
||||
}
|
||||
} else if (type is InterfaceType) {
|
||||
if (elements.add(type.element)) {
|
||||
for (var typeArgument in type.typeArguments) {
|
||||
addElementsFrom(typeArgument);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addElementsFrom(type);
|
||||
_allElements = elements.where((element) {
|
||||
var name = element.name;
|
||||
return name != null && name.isNotEmpty;
|
||||
}).toList();
|
||||
}
|
||||
return _allElements!;
|
||||
}
|
||||
|
||||
addElementsFrom(_type);
|
||||
return elements.where((element) {
|
||||
var name = element.name;
|
||||
return name != null && name.isNotEmpty;
|
||||
});
|
||||
}();
|
||||
|
||||
_TypeToConvert(this.index, this._type, this.displayName);
|
||||
}
|
||||
|
||||
@@ -117,21 +117,33 @@ class ExtensionMemberResolver {
|
||||
}
|
||||
|
||||
// The most specific extension is ambiguous.
|
||||
_errorReporter.atEntity(
|
||||
nameEntity,
|
||||
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS,
|
||||
arguments: [
|
||||
name.name,
|
||||
mostSpecific.map((e) {
|
||||
var name = e.extension.name;
|
||||
if (name != null) {
|
||||
return "extension '$name'";
|
||||
}
|
||||
var type = e.extension.extendedType.getDisplayString();
|
||||
return "unnamed extension on '$type'";
|
||||
}).commaSeparatedWithAnd,
|
||||
],
|
||||
);
|
||||
if (mostSpecific.length == 2) {
|
||||
_errorReporter.atEntity(
|
||||
nameEntity,
|
||||
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO,
|
||||
arguments: [
|
||||
name.name,
|
||||
mostSpecific[0].extension,
|
||||
mostSpecific[1].extension,
|
||||
],
|
||||
);
|
||||
} else {
|
||||
_errorReporter.atEntity(
|
||||
nameEntity,
|
||||
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_THREE_OR_MORE,
|
||||
arguments: [
|
||||
name.name,
|
||||
mostSpecific.map((e) {
|
||||
var name = e.extension.name;
|
||||
if (name != null) {
|
||||
return "extension '$name'";
|
||||
}
|
||||
var type = e.extension.extendedType.getDisplayString();
|
||||
return "unnamed extension on '$type'";
|
||||
}).commaSeparatedWithAnd,
|
||||
],
|
||||
);
|
||||
}
|
||||
return ResolutionResult.ambiguous;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,14 +70,31 @@ class CompileTimeErrorCode extends AnalyzerErrorCode {
|
||||
/// Parameters:
|
||||
/// 0: the name of the member
|
||||
/// 1: the names of the declaring extensions
|
||||
static const CompileTimeErrorCode AMBIGUOUS_EXTENSION_MEMBER_ACCESS =
|
||||
CompileTimeErrorCode(
|
||||
static const CompileTimeErrorCode
|
||||
AMBIGUOUS_EXTENSION_MEMBER_ACCESS_THREE_OR_MORE = CompileTimeErrorCode(
|
||||
'AMBIGUOUS_EXTENSION_MEMBER_ACCESS',
|
||||
"A member named '{0}' is defined in {1}, and none are more specific.",
|
||||
correctionMessage:
|
||||
"Try using an extension override to specify the extension you want to "
|
||||
"be chosen.",
|
||||
hasPublishedDocs: true,
|
||||
uniqueName: 'AMBIGUOUS_EXTENSION_MEMBER_ACCESS_THREE_OR_MORE',
|
||||
);
|
||||
|
||||
/// Parameters:
|
||||
/// 0: the name of the member
|
||||
/// 1: the name of the first declaring extension
|
||||
/// 2: the names of the second declaring extension
|
||||
static const CompileTimeErrorCode AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO =
|
||||
CompileTimeErrorCode(
|
||||
'AMBIGUOUS_EXTENSION_MEMBER_ACCESS',
|
||||
"A member named '{0}' is defined in '{1}' and '{2}', and neither is more "
|
||||
"specific.",
|
||||
correctionMessage:
|
||||
"Try using an extension override to specify the extension you want to "
|
||||
"be chosen.",
|
||||
hasPublishedDocs: true,
|
||||
uniqueName: 'AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO',
|
||||
);
|
||||
|
||||
/// Parameters:
|
||||
|
||||
@@ -49,7 +49,8 @@ const List<ErrorCode> errorCodeValues = [
|
||||
CompileTimeErrorCode.ABSTRACT_FIELD_INITIALIZER,
|
||||
CompileTimeErrorCode.ABSTRACT_SUPER_MEMBER_REFERENCE,
|
||||
CompileTimeErrorCode.AMBIGUOUS_EXPORT,
|
||||
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS,
|
||||
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_THREE_OR_MORE,
|
||||
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO,
|
||||
CompileTimeErrorCode.AMBIGUOUS_IMPORT,
|
||||
CompileTimeErrorCode.AMBIGUOUS_SET_OR_MAP_LITERAL_BOTH,
|
||||
CompileTimeErrorCode.AMBIGUOUS_SET_OR_MAP_LITERAL_EITHER,
|
||||
|
||||
@@ -408,14 +408,16 @@ CompileTimeErrorCode:
|
||||
export 'a.dart';
|
||||
export 'b.dart' hide C;
|
||||
```
|
||||
AMBIGUOUS_EXTENSION_MEMBER_ACCESS:
|
||||
problemMessage: "A member named '{0}' is defined in {1}, and none are more specific."
|
||||
AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO:
|
||||
sharedName: AMBIGUOUS_EXTENSION_MEMBER_ACCESS
|
||||
problemMessage: "A member named '{0}' is defined in '{1}' and '{2}', and neither is more specific."
|
||||
correctionMessage: Try using an extension override to specify the extension you want to be chosen.
|
||||
hasPublishedDocs: true
|
||||
comment: |-
|
||||
Parameters:
|
||||
0: the name of the member
|
||||
1: the names of the declaring extensions
|
||||
1: the name of the first declaring extension
|
||||
2: the names of the second declaring extension
|
||||
documentation: |-
|
||||
#### Description
|
||||
|
||||
@@ -471,6 +473,15 @@ CompileTimeErrorCode:
|
||||
print(E2(s).charCount);
|
||||
}
|
||||
```
|
||||
AMBIGUOUS_EXTENSION_MEMBER_ACCESS_THREE_OR_MORE:
|
||||
sharedName: AMBIGUOUS_EXTENSION_MEMBER_ACCESS
|
||||
problemMessage: "A member named '{0}' is defined in {1}, and none are more specific."
|
||||
correctionMessage: Try using an extension override to specify the extension you want to be chosen.
|
||||
hasPublishedDocs: true
|
||||
comment: |-
|
||||
Parameters:
|
||||
0: the name of the member
|
||||
1: the names of the declaring extensions
|
||||
AMBIGUOUS_IMPORT:
|
||||
problemMessage: "The name '{0}' is defined in the libraries {1}."
|
||||
correctionMessage: "Try using 'as prefix' for one of the import directives, or hiding the name from all but one of the imports."
|
||||
|
||||
@@ -29,7 +29,7 @@ extension E2 on A {
|
||||
|
||||
int f(A a) => a();
|
||||
''', [
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 110, 1),
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO, 110, 1),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ f() {
|
||||
0.a;
|
||||
}
|
||||
''', [
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 98, 1),
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO, 98, 1),
|
||||
]);
|
||||
|
||||
var node = findNode.propertyAccess('0.a');
|
||||
@@ -111,7 +111,7 @@ f() {
|
||||
0.a;
|
||||
}
|
||||
''', [
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 91, 1),
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO, 91, 1),
|
||||
]);
|
||||
|
||||
var node = findNode.propertyAccess('0.a');
|
||||
@@ -144,7 +144,7 @@ f() {
|
||||
0.a;
|
||||
}
|
||||
''', [
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 96, 1),
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO, 96, 1),
|
||||
]);
|
||||
|
||||
var node = findNode.propertyAccess('0.a');
|
||||
@@ -172,8 +172,38 @@ void f() {
|
||||
0.foo();
|
||||
}
|
||||
''', [
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 129, 3,
|
||||
messageContains: ["in extension 'E1' and extension 'E2',"]),
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO, 129, 3,
|
||||
messageContains: [
|
||||
"in 'extension E1 on int' and 'extension E2 on int',",
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
test_method_conflict_conflict_notSpecific_sameName() async {
|
||||
var one = newFile('$testPackageLibPath/one.dart', '''
|
||||
extension E on int { void foo() {} }
|
||||
''');
|
||||
var two = newFile('$testPackageLibPath/two.dart', '''
|
||||
extension E on int { void foo() {} }
|
||||
''');
|
||||
await assertErrorsInCode('''
|
||||
// ignore_for_file: unused_import
|
||||
import 'one.dart';
|
||||
import 'two.dart';
|
||||
void f() {
|
||||
0.foo();
|
||||
}
|
||||
''', [
|
||||
error(
|
||||
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO,
|
||||
87,
|
||||
3,
|
||||
messageContains: [
|
||||
"'extension E on int (where E is defined in ${one.path})' and "
|
||||
"'extension E on int (where E is defined in ${two.path})',",
|
||||
],
|
||||
contextMessages: [message(one, 10, 1), message(two, 10, 1)],
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -197,8 +227,10 @@ void f() {
|
||||
0.foo();
|
||||
}
|
||||
''', [
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 129, 3,
|
||||
messageContains: ["in extension 'E1' and extension 'E2',"]),
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO, 129, 3,
|
||||
messageContains: [
|
||||
"in 'extension E1 on int' and 'extension E2 on int',",
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -227,7 +259,7 @@ f() {
|
||||
0.a();
|
||||
}
|
||||
''', [
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 88, 1),
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO, 88, 1),
|
||||
]);
|
||||
|
||||
var node = findNode.methodInvocation('0.a()');
|
||||
@@ -259,8 +291,10 @@ void f() {
|
||||
0.foo();
|
||||
}
|
||||
''', [
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 129, 3,
|
||||
messageContains: ["in extension 'E1' and extension 'E2',"]),
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO, 129, 3,
|
||||
messageContains: [
|
||||
"in 'extension E1 on int' and 'extension E2 on int',"
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -274,7 +308,10 @@ void f() {
|
||||
0.foo();
|
||||
}
|
||||
''', [
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 167, 3,
|
||||
error(
|
||||
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_THREE_OR_MORE,
|
||||
167,
|
||||
3,
|
||||
messageContains: [
|
||||
"in extension 'E1', extension 'E2', and extension 'E3',"
|
||||
]),
|
||||
@@ -313,7 +350,7 @@ f(SubTarget<num> t) {
|
||||
t.foo;
|
||||
}
|
||||
''', [
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 396, 3),
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO, 396, 3),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -331,7 +368,7 @@ extension E2 on A {
|
||||
|
||||
A f(A a) => a + a;
|
||||
''', [
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 122, 5),
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO, 122, 5),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -351,7 +388,7 @@ void f(A a) {
|
||||
a += 0;
|
||||
}
|
||||
''', [
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 130, 2),
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO, 130, 2),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -369,7 +406,7 @@ extension E2 on A {
|
||||
|
||||
int f(A a) => a[0];
|
||||
''', [
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 134, 1),
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO, 134, 1),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -387,7 +424,7 @@ f() {
|
||||
0[1] += 2;
|
||||
}
|
||||
''', [
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 136, 1),
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO, 136, 1),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -405,7 +442,7 @@ extension E2 on A {
|
||||
|
||||
int f(A a) => -a;
|
||||
''', [
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 123, 1),
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO, 123, 1),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -423,7 +460,7 @@ f() {
|
||||
0.a = 3;
|
||||
}
|
||||
''', [
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 88, 1),
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO, 88, 1),
|
||||
]);
|
||||
|
||||
assertResolvedNodeText(findNode.assignment('= 3'), r'''
|
||||
@@ -476,10 +513,9 @@ int f(List<C> x) => x();
|
||||
int g(List<A> x) => x();
|
||||
int h(List<B> x) => x();
|
||||
''', [
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 167, 1,
|
||||
error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS_TWO, 167, 1,
|
||||
messageContains: [
|
||||
"in unnamed extension on 'List<A>' and unnamed extension on "
|
||||
"'List<B>',"
|
||||
"'extension on List<A>' and 'extension on List<B>',"
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -430,6 +430,9 @@ export 'b.dart' hide C;
|
||||
|
||||
### ambiguous_extension_member_access
|
||||
|
||||
_A member named '{0}' is defined in '{1}' and '{2}', and neither is more
|
||||
specific._
|
||||
|
||||
_A member named '{0}' is defined in {1}, and none are more specific._
|
||||
|
||||
#### Description
|
||||
|
||||
Reference in New Issue
Block a user