Version 2.14.0-84.0.dev
Merge commit '2efbd99c4799b087669b284c28e6644ff8f84807' into 'dev'
diff --git a/pkg/analysis_server/test/tool/completion_metrics/metrics_util_test.dart b/pkg/analysis_server/test/tool/completion_metrics/metrics_util_test.dart
index 8c83b04..265ac05 100644
--- a/pkg/analysis_server/test/tool/completion_metrics/metrics_util_test.dart
+++ b/pkg/analysis_server/test/tool/completion_metrics/metrics_util_test.dart
@@ -76,6 +76,36 @@
});
});
+ group('DistributionComputer', () {
+ test('displayString', () {
+ var computer = DistributionComputer();
+ expect(
+ computer.displayString(),
+ '[0] 0 [10] 0 [20] 0 [30] 0 [40] 0 [50] 0 '
+ '[60] 0 [70] 0 [80] 0 [90] 0 [100] 0');
+
+ for (var value in [
+ 3, // 0-9
+ 12, 15, // 10-19
+ 23, 24, 26, // 20-29
+ 30, 31, 31, 35, // 30-39
+ 42, 42, 42, 42, 42, // 40-49
+ 52, 53, 54, 55, 56, 57, // 50-59
+ 63, // 60-69
+ 72, 79, // 70-79
+ 83, 84, 86, // 80-89
+ 90, 91, 91, 99, // 90-99
+ 100, 110, 120, 5000, // 100+
+ ]) {
+ computer.addValue(value);
+ }
+ expect(
+ computer.displayString(),
+ '[0] 1 [10] 2 [20] 3 [30] 4 [40] 5 [50] 6 '
+ '[60] 1 [70] 2 [80] 3 [90] 4 [100] 4');
+ });
+ });
+
group('MeanReciprocalRankComputer', () {
test('empty', () {
var computer = MeanReciprocalRankComputer('');
diff --git a/pkg/analysis_server/tool/code_completion/completion_metrics.dart b/pkg/analysis_server/tool/code_completion/completion_metrics.dart
index 99eea81..434a3c1 100644
--- a/pkg/analysis_server/tool/code_completion/completion_metrics.dart
+++ b/pkg/analysis_server/tool/code_completion/completion_metrics.dart
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:convert';
-import 'dart:io' as io;
+import 'dart:developer';
import 'dart:math' as math;
import 'package:_fe_analyzer_shared/src/base/syntactic_entity.dart';
@@ -62,7 +62,7 @@
var result = parser.parse(args);
if (!validArguments(parser, result)) {
- io.exit(1);
+ return;
}
var options = CompletionMetricsOptions(result);
@@ -98,7 +98,7 @@
print('Analyzing root: "$rootPath"');
var stopwatch = Stopwatch()..start();
var computer = CompletionMetricsComputer(rootPath, options);
- var code = await computer.computeMetrics();
+ await computer.computeMetrics();
stopwatch.stop();
var duration = Duration(milliseconds: stopwatch.elapsedMilliseconds);
@@ -113,7 +113,6 @@
} else {
computer.printResults();
}
- io.exit(code);
}
/// A [Counter] to track the performance of each of the completion strategies
@@ -282,6 +281,9 @@
/// The function to be executed when this metrics collector is disabled.
final void Function()? disableFunction;
+ /// The tag used to profile performance of completions for this set of metrics.
+ final UserTag userTag;
+
final Counter completionCounter = Counter('all completions');
final Counter completionMissedTokenCounter =
@@ -296,6 +298,8 @@
final ArithmeticMeanComputer meanCompletionMS =
ArithmeticMeanComputer('ms per completion');
+ final DistributionComputer distributionCompletionMS = DistributionComputer();
+
final MeanReciprocalRankComputer mrrComputer =
MeanReciprocalRankComputer('all completions');
@@ -333,7 +337,8 @@
CompletionMetrics(this.name,
{required this.availableSuggestions,
this.enableFunction,
- this.disableFunction});
+ this.disableFunction})
+ : userTag = UserTag(name);
/// Return an instance extracted from the decoded JSON [map].
factory CompletionMetrics.fromJson(Map<String, dynamic> map) {
@@ -349,6 +354,8 @@
.fromJson(map['completionElementKindCounter'] as Map<String, dynamic>);
metrics.meanCompletionMS
.fromJson(map['meanCompletionMS'] as Map<String, dynamic>);
+ metrics.distributionCompletionMS
+ .fromJson(map['distributionCompletionMS'] as Map<String, dynamic>);
metrics.mrrComputer.fromJson(map['mrrComputer'] as Map<String, dynamic>);
metrics.successfulMrrComputer
.fromJson(map['successfulMrrComputer'] as Map<String, dynamic>);
@@ -401,6 +408,7 @@
completionKindCounter.addData(metrics.completionKindCounter);
completionElementKindCounter.addData(metrics.completionElementKindCounter);
meanCompletionMS.addData(metrics.meanCompletionMS);
+ distributionCompletionMS.addData(metrics.distributionCompletionMS);
mrrComputer.addData(metrics.mrrComputer);
successfulMrrComputer.addData(metrics.successfulMrrComputer);
for (var entry in metrics.groupMrrComputers.entries) {
@@ -471,6 +479,7 @@
'completionKindCounter': completionKindCounter.toJson(),
'completionElementKindCounter': completionElementKindCounter.toJson(),
'meanCompletionMS': meanCompletionMS.toJson(),
+ 'distributionCompletionMS': distributionCompletionMS.toJson(),
'mrrComputer': mrrComputer.toJson(),
'successfulMrrComputer': successfulMrrComputer.toJson(),
'groupMrrComputers': groupMrrComputers
@@ -542,6 +551,7 @@
/// Record this elapsed ms count for the average ms count.
void _recordTime(CompletionResult result) {
meanCompletionMS.addValue(result.elapsedMS);
+ distributionCompletionMS.addValue(result.elapsedMS);
}
/// If the [result] is worse than any previously recorded results, record it.
@@ -568,9 +578,6 @@
late ResolvedUnitResult _resolvedUnitResult;
- /// The int to be returned from the [computeMetrics] call.
- int resultCode = 0;
-
/// A list of the metrics to be computed.
final List<CompletionMetrics> targetMetrics = [];
@@ -622,8 +629,7 @@
}
}
- Future<int> computeMetrics() async {
- resultCode = 0;
+ Future<void> computeMetrics() async {
// To compare two or more changes to completions, add a `CompletionMetrics`
// object with enable and disable functions to the list of `targetMetrics`.
targetMetrics.add(CompletionMetrics('shipping',
@@ -647,7 +653,6 @@
for (var context in collection.contexts) {
await _computeInContext(context.contextRoot);
}
- return resultCode;
}
int forEachExpectedCompletion(
@@ -791,6 +796,9 @@
printCounter(metrics.completionElementKindCounter);
}
+ var distribution = metrics.distributionCompletionMS.displayString();
+ print('${metrics.name}: $distribution');
+
List<String> toRow(MeanReciprocalRankComputer computer) {
return [
computer.name,
@@ -948,6 +956,11 @@
printHeading(2, 'Comparison of other metrics');
printTable(table);
+
+ for (var metrics in targetMetrics) {
+ var distribution = metrics.distributionCompletionMS.displayString();
+ print('${metrics.name}: $distribution');
+ }
}
void printResults() {
@@ -1140,7 +1153,6 @@
print('File $filePath skipped due to errors such as:');
print(' ${analysisError.toString()}');
print('');
- resultCode = 1;
continue;
}
@@ -1214,9 +1226,11 @@
var bestRank = -1;
var bestName = '';
+ var defaultTag = getCurrentTag();
for (var metrics in targetMetrics) {
// Compute the completions.
metrics.enable();
+ metrics.userTag.makeCurrent();
// if (FeatureComputer.noDisabledFeatures) {
// var line = expectedCompletion.lineNumber;
// var column = expectedCompletion.columnNumber;
@@ -1229,6 +1243,7 @@
bestRank = rank;
bestName = metrics.name;
}
+ defaultTag.makeCurrent();
metrics.disable();
}
rankComparison.count(bestName);
@@ -1243,7 +1258,6 @@
print('Exception caught analyzing: $filePath');
print(exception.toString());
print(stackTrace);
- resultCode = 1;
}
}
}
diff --git a/pkg/analysis_server/tool/code_completion/metrics_util.dart b/pkg/analysis_server/tool/code_completion/metrics_util.dart
index c3b01717..ab038b0 100644
--- a/pkg/analysis_server/tool/code_completion/metrics_util.dart
+++ b/pkg/analysis_server/tool/code_completion/metrics_util.dart
@@ -50,10 +50,6 @@
max = map['max'] as int?;
}
- void printMean() {
- print('Mean \'$name\' ${mean.toStringAsFixed(6)} (total = $count)');
- }
-
/// Return a map used to represent this computer in a JSON structure.
Map<String, dynamic> toJson() {
return {
@@ -174,6 +170,52 @@
}
}
+class DistributionComputer {
+ /// The buckets in which values are counted: [0..9], [10..19], ... [100..].
+ List<int> buckets = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+
+ /// Add the data from the given [computer] to this computer.
+ void addData(DistributionComputer computer) {
+ for (var i = 0; i < buckets.length; i++) {
+ buckets[i] += computer.buckets[i];
+ }
+ }
+
+ /// Add a millisecond value to the list of buckets.
+ void addValue(int value) {
+ var bucket = math.min(value ~/ 10, buckets.length - 1);
+ buckets[bucket]++;
+ }
+
+ /// Return a textual representation of the distribution.
+ String displayString() {
+ var buffer = StringBuffer();
+ for (var i = 0; i < buckets.length; i++) {
+ if (i > 0) {
+ buffer.write(' ');
+ }
+ buffer.write('[');
+ buffer.write(i * 10);
+ buffer.write('] ');
+ buffer.write(buckets[i]);
+ }
+ return buffer.toString();
+ }
+
+ /// Set the state of this computer to the state recorded in the decoded JSON
+ /// [map].
+ void fromJson(Map<String, dynamic> map) {
+ buckets = map['buckets'] as List<int>;
+ }
+
+ /// Return a map used to represent this computer in a JSON structure.
+ Map<String, dynamic> toJson() {
+ return {
+ 'buckets': buckets,
+ };
+ }
+}
+
/// A computer for the mean reciprocal rank. The MRR as well as the MRR only
/// if the item was in the top 5 in the list see [MAX_RANK], is computed.
/// https://en.wikipedia.org/wiki/Mean_reciprocal_rank.
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
diff --git a/tools/VERSION b/tools/VERSION
index 6e2781c..f487dbc 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 14
PATCH 0
-PRERELEASE 83
+PRERELEASE 84
PRERELEASE_PATCH 0
\ No newline at end of file