Issue 45887. Report AMBIGUOUS_EXTENSION_MEMBER_ACCESS with actually conflicting extensions.
Bug: https://github.com/dart-lang/sdk/issues/45887
Change-Id: I8ec587b4479e165056766d433cb0a3c459d5f504
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/198240
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/lib/src/dart/resolver/extension_member_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/extension_member_resolver.dart
index c5013b7..e78d21f 100644
--- a/pkg/analyzer/lib/src/dart/resolver/extension_member_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/extension_member_resolver.dart
@@ -23,6 +23,8 @@
import 'package:analyzer/src/dart/resolver/scope.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/resolver.dart';
+import 'package:analyzer/src/util/either.dart';
+import 'package:analyzer/src/utilities/extensions/string.dart';
class ExtensionMemberResolver {
final ResolverVisitor _resolver;
@@ -64,22 +66,27 @@
return extensions[0].asResolutionResult;
}
- var extension = _chooseMostSpecific(extensions);
- if (extension != null) {
- return extension.asResolutionResult;
- }
-
- _errorReporter.reportErrorForOffset(
- CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS,
- nameEntity.offset,
- nameEntity.length,
- [
- name,
- extensions[0].extension.name,
- extensions[1].extension.name,
- ],
+ var mostSpecific = _chooseMostSpecific(extensions);
+ return mostSpecific.map(
+ (extension) {
+ return extension.asResolutionResult;
+ },
+ (noneMoreSpecific) {
+ _errorReporter.reportErrorForOffset(
+ CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS,
+ nameEntity.offset,
+ nameEntity.length,
+ [
+ name,
+ noneMoreSpecific
+ .map((e) => "'${e.extension.name ?? '<unnamed>'}'")
+ .toList()
+ .commaSeparatedWithAnd,
+ ],
+ );
+ return ResolutionResult.ambiguous;
+ },
);
- return ResolutionResult.ambiguous;
}
/// Resolve the [name] (without `=`) to the corresponding getter and setter
@@ -250,27 +257,48 @@
}
}
- /// Return the most specific extension or `null` if no single one can be
- /// identified.
- _InstantiatedExtension? _chooseMostSpecific(
- List<_InstantiatedExtension> extensions) {
- for (var i = 0; i < extensions.length; i++) {
- var e1 = extensions[i];
- var isMoreSpecific = true;
- for (var j = 0; j < extensions.length; j++) {
- var e2 = extensions[j];
- if (i != j && !_isMoreSpecific(e1, e2)) {
- isMoreSpecific = false;
- break;
+ /// Return either the most specific extension, or a list of the extensions
+ /// that are ambiguous.
+ Either2<_InstantiatedExtension, List<_InstantiatedExtension>>
+ _chooseMostSpecific(List<_InstantiatedExtension> extensions) {
+ _InstantiatedExtension? bestSoFar;
+ var noneMoreSpecific = <_InstantiatedExtension>[];
+ for (var candidate in extensions) {
+ if (noneMoreSpecific.isNotEmpty) {
+ var isMostSpecific = true;
+ var hasMoreSpecific = false;
+ for (var other in noneMoreSpecific) {
+ if (!_isMoreSpecific(candidate, other)) {
+ isMostSpecific = false;
+ }
+ if (_isMoreSpecific(other, candidate)) {
+ hasMoreSpecific = true;
+ }
}
- }
- if (isMoreSpecific) {
- return e1;
+ if (isMostSpecific) {
+ bestSoFar = candidate;
+ noneMoreSpecific.clear();
+ } else if (!hasMoreSpecific) {
+ noneMoreSpecific.add(candidate);
+ }
+ } else if (bestSoFar == null) {
+ bestSoFar = candidate;
+ } else if (_isMoreSpecific(bestSoFar, candidate)) {
+ // already
+ } else if (_isMoreSpecific(candidate, bestSoFar)) {
+ bestSoFar = candidate;
+ } else {
+ noneMoreSpecific.add(bestSoFar);
+ noneMoreSpecific.add(candidate);
+ bestSoFar = null;
}
}
- // Otherwise fail.
- return null;
+ if (bestSoFar != null) {
+ return Either2.t1(bestSoFar);
+ } else {
+ return Either2.t2(noneMoreSpecific);
+ }
}
/// Return extensions for the [type] that match the given [name] in the
diff --git a/pkg/analyzer/lib/src/error/codes.dart b/pkg/analyzer/lib/src/error/codes.dart
index 9c8e2f7..96d81eb 100644
--- a/pkg/analyzer/lib/src/error/codes.dart
+++ b/pkg/analyzer/lib/src/error/codes.dart
@@ -241,19 +241,14 @@
// print(E2(s).charCount);
// }
// ```
- /*
- * TODO(brianwilkerson) This message doesn't handle the possible case where
- * there are more than 2 extensions, nor does it handle well the case where
- * one or more of the extensions is unnamed.
- */
static const CompileTimeErrorCode AMBIGUOUS_EXTENSION_MEMBER_ACCESS =
CompileTimeErrorCode(
'AMBIGUOUS_EXTENSION_MEMBER_ACCESS',
- "A member named '{0}' is defined in extensions '{1}' and '{2}' and "
- "neither is more specific.",
+ "A member named '{0}' is defined in extensions {1}, and "
+ "none are more specific.",
correction:
"Try using an extension override to specify the extension "
- "you want to to be chosen.",
+ "you want to be chosen.",
hasPublishedDocs: true);
/**
diff --git a/pkg/analyzer/test/src/diagnostics/ambiguous_extension_member_access_test.dart b/pkg/analyzer/test/src/diagnostics/ambiguous_extension_member_access_test.dart
index 424912e..6355201 100644
--- a/pkg/analyzer/test/src/diagnostics/ambiguous_extension_member_access_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/ambiguous_extension_member_access_test.dart
@@ -96,6 +96,56 @@
assertTypeDynamic(access);
}
+ test_method_conflict_conflict_notSpecific() async {
+ await assertErrorsInCode('''
+extension E1 on int { void foo() {} }
+extension E2 on int { void foo() {} }
+extension E on int? { void foo() {} }
+void f() {
+ 0.foo();
+}
+''', [
+ error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 129, 3,
+ messageContains: "'E1' and 'E2'"),
+ ]);
+ }
+
+ test_method_conflict_conflict_specific() async {
+ await assertNoErrorsInCode('''
+extension E1 on int? { void foo() {} }
+extension E2 on int? { void foo() {} }
+extension E on int { void foo() {} }
+void f() {
+ 0.foo();
+}
+''');
+ }
+
+ test_method_conflict_notSpecific_conflict() async {
+ await assertErrorsInCode('''
+extension E1 on int { void foo() {} }
+extension E on int? { void foo() {} }
+extension E2 on int { void foo() {} }
+void f() {
+ 0.foo();
+}
+''', [
+ error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 129, 3,
+ messageContains: "'E1' and 'E2'"),
+ ]);
+ }
+
+ test_method_conflict_specific_conflict() async {
+ await assertNoErrorsInCode('''
+extension E1 on int? { void foo() {} }
+extension E on int { void foo() {} }
+extension E2 on int? { void foo() {} }
+void f() {
+ 0.foo();
+}
+''');
+ }
+
test_method_method() async {
await assertErrorsInCode('''
extension E1 on int {
@@ -117,6 +167,46 @@
assertTypeDynamic(invocation);
}
+ test_method_notSpecific_conflict_conflict() async {
+ await assertErrorsInCode('''
+extension E on int? { void foo() {} }
+extension E1 on int { void foo() {} }
+extension E2 on int { void foo() {} }
+void f() {
+ 0.foo();
+}
+''', [
+ error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 129, 3,
+ messageContains: "'E1' and 'E2'"),
+ ]);
+ }
+
+ test_method_notSpecific_conflict_conflict_conflict() async {
+ await assertErrorsInCode('''
+extension E on int? { void foo() {} }
+extension E1 on int { void foo() {} }
+extension E2 on int { void foo() {} }
+extension E3 on int { void foo() {} }
+void f() {
+ 0.foo();
+}
+''', [
+ error(CompileTimeErrorCode.AMBIGUOUS_EXTENSION_MEMBER_ACCESS, 167, 3,
+ messageContains: "'E1', 'E2', and 'E3'"),
+ ]);
+ }
+
+ test_method_specific_conflict_conflict() async {
+ await assertNoErrorsInCode('''
+extension E on int { void foo() {} }
+extension E1 on int? { void foo() {} }
+extension E2 on int? { void foo() {} }
+void f() {
+ 0.foo();
+}
+''');
+ }
+
test_noMoreSpecificExtension() async {
await assertErrorsInCode(r'''
class Target<T> {}
diff --git a/pkg/analyzer/tool/diagnostics/diagnostics.md b/pkg/analyzer/tool/diagnostics/diagnostics.md
index 2ca67b4..1722c990 100644
--- a/pkg/analyzer/tool/diagnostics/diagnostics.md
+++ b/pkg/analyzer/tool/diagnostics/diagnostics.md
@@ -398,8 +398,8 @@
### ambiguous_extension_member_access
-_A member named '{0}' is defined in extensions '{1}' and '{2}' and neither is
-more specific._
+_A member named '{0}' is defined in extensions {1}, and neither is more
+specific._
#### Description