Version 2.13.0-182.0.dev
Merge commit '2a966bcf6bc0be5df5340c9c42d96dc8df3f6918' into 'dev'
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/argument_type_not_assignable_nullability_error.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/argument_type_not_assignable_nullability_error.dart
index e83ebea..1484b55 100644
--- a/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/argument_type_not_assignable_nullability_error.dart
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/argument_type_not_assignable_nullability_error.dart
@@ -401,3 +401,79 @@
/*analyzer.notPromoted(propertyNotPromoted(target: member:C26.bad, type: int?))*/ c
. /*cfe.notPromoted(propertyNotPromoted(target: member:C26.bad, type: int?))*/ bad;
}
+
+class C27 {
+ int? bad;
+}
+
+indexGet(C27 c, List<int> values) {
+ if (c.bad == null) return;
+ values[
+ /*analyzer.notPromoted(propertyNotPromoted(target: member:C27.bad, type: int?))*/ c
+ . /*cfe.notPromoted(propertyNotPromoted(target: member:C27.bad, type: int?))*/ bad];
+}
+
+class C28 {
+ int? bad;
+}
+
+indexSet(C28 c, List<int> values) {
+ if (c.bad == null) return;
+ values[
+ /*analyzer.notPromoted(propertyNotPromoted(target: member:C28.bad, type: int?))*/ c
+ . /*cfe.notPromoted(propertyNotPromoted(target: member:C28.bad, type: int?))*/ bad] = 0;
+}
+
+class C29 {
+ int? bad;
+}
+
+indexSetCompound(C29 c, List<int> values) {
+ // TODO(paulberry): get this to work with the CFE
+ if (c.bad == null) return;
+ values[
+ /*analyzer.notPromoted(propertyNotPromoted(target: member:C29.bad, type: int?))*/ c
+ .bad] += 1;
+}
+
+class C30 {
+ int? bad;
+}
+
+indexSetIfNull(C30 c, List<int?> values) {
+ // TODO(paulberry): get this to work with the CFE
+ if (c.bad == null) return;
+ values[
+ /*analyzer.notPromoted(propertyNotPromoted(target: member:C30.bad, type: int?))*/ c
+ .bad] ??= 1;
+}
+
+class C31 {
+ int? bad;
+}
+
+indexSetPreIncDec(C31 c, List<int> values) {
+ // TODO(paulberry): get this to work with the CFE
+ if (c.bad == null) return;
+ ++values[
+ /*analyzer.notPromoted(propertyNotPromoted(target: member:C31.bad, type: int?))*/ c
+ .bad];
+ --values[
+ /*analyzer.notPromoted(propertyNotPromoted(target: member:C31.bad, type: int?))*/ c
+ .bad];
+}
+
+class C32 {
+ int? bad;
+}
+
+indexSetPostIncDec(C32 c, List<int> values) {
+ // TODO(paulberry): get this to work with the CFE
+ if (c.bad == null) return;
+ values[
+ /*analyzer.notPromoted(propertyNotPromoted(target: member:C32.bad, type: int?))*/ c
+ .bad]++;
+ values[
+ /*analyzer.notPromoted(propertyNotPromoted(target: member:C32.bad, type: int?))*/ c
+ .bad]--;
+}
diff --git a/pkg/analyzer/lib/src/generated/error_detection_helpers.dart b/pkg/analyzer/lib/src/generated/error_detection_helpers.dart
index 350899c..af745c8 100644
--- a/pkg/analyzer/lib/src/generated/error_detection_helpers.dart
+++ b/pkg/analyzer/lib/src/generated/error_detection_helpers.dart
@@ -55,7 +55,7 @@
void checkForArgumentTypeNotAssignableForArgument(Expression argument,
{bool promoteParameterToNullable = false,
Map<DartType, NonPromotionReason> Function()? whyNotPromoted}) {
- checkForArgumentTypeNotAssignableForArgument2(
+ _checkForArgumentTypeNotAssignableForArgument2(
argument: argument,
parameter: argument.staticParameterElement,
promoteParameterToNullable: promoteParameterToNullable,
@@ -63,24 +63,6 @@
);
}
- void checkForArgumentTypeNotAssignableForArgument2({
- required Expression argument,
- required ParameterElement? parameter,
- required bool promoteParameterToNullable,
- Map<DartType, NonPromotionReason> Function()? whyNotPromoted,
- }) {
- var staticParameterType = parameter?.type;
- if (promoteParameterToNullable && staticParameterType != null) {
- staticParameterType =
- typeSystem.makeNullable(staticParameterType as TypeImpl);
- }
- _checkForArgumentTypeNotAssignableWithExpectedTypes(
- argument,
- staticParameterType,
- CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE,
- whyNotPromoted);
- }
-
/// Verify that the given constructor field [initializer] has compatible field
/// and initializer expression types. The [fieldElement] is the static element
/// from the name in the [ConstructorFieldInitializer].
@@ -205,6 +187,36 @@
return true;
}
+ void checkIndexExpressionIndex(
+ Expression index, {
+ required ExecutableElement? readElement,
+ required ExecutableElement? writeElement,
+ required Map<DartType, NonPromotionReason> Function()? whyNotPromoted,
+ }) {
+ if (readElement is MethodElement) {
+ var parameters = readElement.parameters;
+ if (parameters.isNotEmpty) {
+ _checkForArgumentTypeNotAssignableForArgument2(
+ argument: index,
+ parameter: parameters[0],
+ promoteParameterToNullable: false,
+ whyNotPromoted: whyNotPromoted,
+ );
+ }
+ }
+
+ if (writeElement is MethodElement) {
+ var parameters = writeElement.parameters;
+ if (parameters.isNotEmpty) {
+ _checkForArgumentTypeNotAssignableForArgument2(
+ argument: index,
+ parameter: parameters[0],
+ promoteParameterToNullable: false,
+ );
+ }
+ }
+ }
+
/// Computes the appropriate set of context messages to report along with an
/// error that may have occurred because [expression] was not type promoted.
///
@@ -233,6 +245,24 @@
return null;
}
+ void _checkForArgumentTypeNotAssignableForArgument2({
+ required Expression argument,
+ required ParameterElement? parameter,
+ required bool promoteParameterToNullable,
+ Map<DartType, NonPromotionReason> Function()? whyNotPromoted,
+ }) {
+ var staticParameterType = parameter?.type;
+ if (promoteParameterToNullable && staticParameterType != null) {
+ staticParameterType =
+ typeSystem.makeNullable(staticParameterType as TypeImpl);
+ }
+ _checkForArgumentTypeNotAssignableWithExpectedTypes(
+ argument,
+ staticParameterType,
+ CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE,
+ whyNotPromoted);
+ }
+
/// Verify that the given [expression] can be assigned to its corresponding
/// parameters.
///
diff --git a/pkg/analyzer/lib/src/generated/error_verifier.dart b/pkg/analyzer/lib/src/generated/error_verifier.dart
index 307171d..c2e7559 100644
--- a/pkg/analyzer/lib/src/generated/error_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/error_verifier.dart
@@ -349,13 +349,6 @@
_checkForDeadNullCoalesce(node.readType as TypeImpl, node.rightHandSide);
}
_checkForAssignmentToFinal(lhs);
- if (lhs is IndexExpression) {
- _checkIndexExpressionIndex(
- lhs.index,
- readElement: node.readElement as ExecutableElement?,
- writeElement: node.writeElement as ExecutableElement?,
- );
- }
super.visitAssignmentExpression(node);
}
@@ -830,12 +823,6 @@
@override
void visitIndexExpression(IndexExpression node) {
- _checkIndexExpressionIndex(
- node.index,
- readElement: node.staticElement,
- writeElement: null,
- );
-
if (node.isNullAware) {
_checkForUnnecessaryNullAware(
node.realTarget,
@@ -1006,13 +993,6 @@
_checkForAssignmentToFinal(operand);
_checkForIntNotAssignable(operand);
}
- if (operand is IndexExpression) {
- _checkIndexExpressionIndex(
- operand.index,
- readElement: node.readElement as ExecutableElement?,
- writeElement: node.writeElement as ExecutableElement?,
- );
- }
super.visitPostfixExpression(node);
}
@@ -1038,13 +1018,6 @@
checkForUseOfVoidResult(operand);
_checkForIntNotAssignable(operand);
}
- if (operand is IndexExpression) {
- _checkIndexExpressionIndex(
- operand.index,
- readElement: node.readElement as ExecutableElement?,
- writeElement: node.writeElement as ExecutableElement?,
- );
- }
super.visitPrefixExpression(node);
}
@@ -4594,34 +4567,6 @@
}
}
- void _checkIndexExpressionIndex(
- Expression index, {
- required ExecutableElement? readElement,
- required ExecutableElement? writeElement,
- }) {
- if (readElement is MethodElement) {
- var parameters = readElement.parameters;
- if (parameters.isNotEmpty) {
- checkForArgumentTypeNotAssignableForArgument2(
- argument: index,
- parameter: parameters[0],
- promoteParameterToNullable: false,
- );
- }
- }
-
- if (writeElement is MethodElement) {
- var parameters = writeElement.parameters;
- if (parameters.isNotEmpty) {
- checkForArgumentTypeNotAssignableForArgument2(
- argument: index,
- parameter: parameters[0],
- promoteParameterToNullable: false,
- );
- }
- }
- }
-
void _checkMixinInference(
NamedCompilationUnitMember node, WithClause? withClause) {
if (withClause == null) {
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index 5684e0e..211323d 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -709,6 +709,13 @@
InferenceContext.setType(node.index, result.indexContextType);
node.index.accept(this);
+ var whyNotPromoted = flowAnalysis?.flow?.whyNotPromoted(node.index);
+ checkIndexExpressionIndex(
+ node.index,
+ readElement: result.readElement as ExecutableElement?,
+ writeElement: result.writeElement as ExecutableElement?,
+ whyNotPromoted: whyNotPromoted,
+ );
return result;
} else if (node is PrefixedIdentifier) {
@@ -1702,6 +1709,13 @@
InferenceContext.setType(node.index, result.indexContextType);
node.index.accept(this);
+ var whyNotPromoted = flowAnalysis?.flow?.whyNotPromoted(node.index);
+ checkIndexExpressionIndex(
+ node.index,
+ readElement: result.readElement as ExecutableElement?,
+ writeElement: null,
+ whyNotPromoted: whyNotPromoted,
+ );
DartType type;
if (identical(node.realTarget.staticType, NeverTypeImpl.instance)) {
diff --git a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
index a9f8b0f..8c3a9a5 100644
--- a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
+++ b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
@@ -459,7 +459,7 @@
int get length => 0;
Iterable<V> get values;
- V? operator [](K key);
+ V? operator [](Object? key);
void operator []=(K key, V value);
Map<RK, RV> cast<RK, RV>();
diff --git a/pkg/analyzer/test/src/diagnostics/argument_type_not_assignable_test.dart b/pkg/analyzer/test/src/diagnostics/argument_type_not_assignable_test.dart
index 1225bc4..096fcdc 100644
--- a/pkg/analyzer/test/src/diagnostics/argument_type_not_assignable_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/argument_type_not_assignable_test.dart
@@ -454,6 +454,40 @@
]);
}
+ test_map_indexGet() async {
+ // Any type may be passed to Map.operator[].
+ await assertNoErrorsInCode('''
+main() {
+ Map<int, int> m = <int, int>{};
+ m['x'];
+}
+''');
+ }
+
+ test_map_indexSet() async {
+ // The type passed to Map.operator[]= must match the key type.
+ await assertErrorsInCode('''
+main() {
+ Map<int, int> m = <int, int>{};
+ m['x'] = 0;
+}
+''', [
+ error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 47, 3),
+ ]);
+ }
+
+ test_map_indexSet_ifNull() async {
+ // The type passed to Map.operator[]= must match the key type.
+ await assertErrorsInCode('''
+main() {
+ Map<int, int> m = <int, int>{};
+ m['x'] ??= 0;
+}
+''', [
+ error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 47, 3),
+ ]);
+ }
+
test_new_generic() async {
await assertErrorsInCode('''
class A<T> {
diff --git a/pkg/compiler/lib/src/js_emitter/code_emitter_task.dart b/pkg/compiler/lib/src/js_emitter/code_emitter_task.dart
index 3b14775..d52a591 100644
--- a/pkg/compiler/lib/src/js_emitter/code_emitter_task.dart
+++ b/pkg/compiler/lib/src/js_emitter/code_emitter_task.dart
@@ -213,6 +213,9 @@
List<PreFragment> get preDeferredFragmentsForTesting;
+ /// The set of omitted [OutputUnits].
+ Set<OutputUnit> get omittedOutputUnits;
+
/// A map of loadId to list of [FinalizedFragments].
Map<String, List<FinalizedFragment>> get finalizedFragmentsToLoad;
diff --git a/pkg/compiler/lib/src/js_emitter/startup_emitter/emitter.dart b/pkg/compiler/lib/src/js_emitter/startup_emitter/emitter.dart
index 17184dd..01c863f 100644
--- a/pkg/compiler/lib/src/js_emitter/startup_emitter/emitter.dart
+++ b/pkg/compiler/lib/src/js_emitter/startup_emitter/emitter.dart
@@ -160,6 +160,9 @@
List<PreFragment> preDeferredFragmentsForTesting;
@override
+ Set<OutputUnit> omittedOutputUnits;
+
+ @override
Map<String, List<FinalizedFragment>> finalizedFragmentsToLoad;
@override
@@ -206,6 +209,7 @@
}
return _task.measureSubtask('emit program', () {
var size = _emitter.emitProgram(program, codegenWorld);
+ omittedOutputUnits = _emitter.omittedOutputUnits;
finalizedFragmentsToLoad = _emitter.finalizedFragmentsToLoad;
fragmentMerger = _emitter.fragmentMerger;
finalizedFragmentsToLoad.values.forEach((fragments) {
diff --git a/pkg/compiler/test/deferred_loading/deferred_loading_test.dart b/pkg/compiler/test/deferred_loading/deferred_loading_test.dart
index 374d091..dcd98df 100644
--- a/pkg/compiler/test/deferred_loading/deferred_loading_test.dart
+++ b/pkg/compiler/test/deferred_loading/deferred_loading_test.dart
@@ -156,9 +156,12 @@
.backendStrategy.emitterTask.emitter.preDeferredFragmentsForTesting;
Map<String, List<FinalizedFragment>> fragmentsToLoad =
compiler.backendStrategy.emitterTask.emitter.finalizedFragmentsToLoad;
+ Set<OutputUnit> omittedOutputUnits =
+ compiler.backendStrategy.emitterTask.emitter.omittedOutputUnits;
Map<String, List<PreFragment>> preFragmentMap =
buildPreFragmentMap(fragmentsToLoad, preDeferredFragments);
- PreFragmentsIrComputer(compiler.reporter, actualMap, preFragmentMap)
+ PreFragmentsIrComputer(
+ compiler.reporter, actualMap, preFragmentMap, omittedOutputUnits)
.computeForLibrary(node);
}
@@ -169,9 +172,13 @@
class PreFragmentsIrComputer extends IrDataExtractor<Features> {
final Map<String, List<PreFragment>> _preFragmentMap;
+ final Set<OutputUnit> _omittedOutputUnits;
- PreFragmentsIrComputer(DiagnosticReporter reporter,
- Map<Id, ActualData<Features>> actualMap, this._preFragmentMap)
+ PreFragmentsIrComputer(
+ DiagnosticReporter reporter,
+ Map<Id, ActualData<Features>> actualMap,
+ this._preFragmentMap,
+ this._omittedOutputUnits)
: super(reporter, actualMap);
@override
@@ -214,11 +221,16 @@
}
for (var emittedOutputUnit in preFragment.emittedOutputUnits) {
- supplied.add(emittedOutputUnit.outputUnit);
+ var outputUnit = emittedOutputUnit.outputUnit;
+ if (!_omittedOutputUnits.contains(outputUnit)) {
+ supplied.add(outputUnit);
+ }
}
- var suppliedString = '[${supplied.map(outputUnitString).join(', ')}]';
- features.addElement(Tags.outputUnits,
- 'f$i: {units: $suppliedString, usedBy: $usedBy, needs: $needs}');
+ if (supplied.isNotEmpty) {
+ var suppliedString = '[${supplied.map(outputUnitString).join(', ')}]';
+ features.addElement(Tags.outputUnits,
+ 'f$i: {units: $suppliedString, usedBy: $usedBy, needs: $needs}');
+ }
}
return features;
diff --git a/pkg/test_runner/lib/src/command_output.dart b/pkg/test_runner/lib/src/command_output.dart
index 0e85567..0f9738e 100644
--- a/pkg/test_runner/lib/src/command_output.dart
+++ b/pkg/test_runner/lib/src/command_output.dart
@@ -984,8 +984,9 @@
///
/// The test runner only validates the main error message, and not the
/// suggested fixes, so we only parse the first line.
+ // TODO(rnystrom): Support validating context messages.
static final _errorRegexp =
- RegExp(r"^([^:]+):(\d+):(\d+):\nError: (.*)$", multiLine: true);
+ RegExp(r"^([^:]+):(\d+):(\d+):\n(Error): (.*)$", multiLine: true);
Dart2jsCompilerCommandOutput(
Command command,
@@ -1017,8 +1018,9 @@
///
/// The test runner only validates the main error message, and not the
/// suggested fixes, so we only parse the first line.
+ // TODO(rnystrom): Support validating context messages.
static final _errorRegexp = RegExp(
- r"^org-dartlang-app:/([^:]+):(\d+):(\d+): Error: (.*)$",
+ r"^org-dartlang-app:/([^:]+):(\d+):(\d+): (Error): (.*)$",
multiLine: true);
DevCompilerCommandOutput(
@@ -1252,36 +1254,22 @@
static void parseErrors(
String stdout, List<StaticError> errors, List<StaticError> warnings) {
_StaticErrorOutput._parseCfeErrors(
- ErrorSource.cfe, _errorRegexp, stdout, errors);
- _StaticErrorOutput._parseCfeErrors(
- ErrorSource.cfe, _warningRegexp, stdout, warnings);
+ ErrorSource.cfe, _errorRegexp, stdout, errors, warnings);
}
- /// Matches the first line of a Fasta error message. Fasta prints errors to
- /// stdout that look like:
+ /// Matches the first line of a Fasta error, warning, or context message.
+ /// Fasta prints to stdout like:
///
/// tests/language_2/some_test.dart:7:21: Error: Some message.
/// Try fixing the code to be less bad.
/// var _ = <int>[if (1) 2];
/// ^
///
- /// The test runner only validates the main error message, and not the
- /// suggested fixes, so we only parse the first line.
- static final _errorRegexp =
- RegExp(r"^([^:]+):(\d+):(\d+): Error: (.*)$", multiLine: true);
-
- /// Matches the first line of a Fasta warning message. Fasta prints errors to
- /// stdout that look like:
- ///
- /// tests/language_2/some_test.dart:7:21: Warning: Some message.
- /// Try fixing the code to be less bad.
- /// var _ = <int>[if (1) 2];
- /// ^
- ///
- /// The test runner only validates the main error message, and not the
- /// suggested fixes, so we only parse the first line.
- static final _warningRegexp =
- RegExp(r"^([^:]+):(\d+):(\d+): Warning: (.*)$", multiLine: true);
+ /// The test runner only validates the first line of the message, and not the
+ /// suggested fixes.
+ static final _errorRegexp = RegExp(
+ r"^([^:]+):(\d+):(\d+): (Context|Error|Warning): (.*)$",
+ multiLine: true);
FastaCommandOutput(
Command command,
@@ -1310,12 +1298,35 @@
/// Parses compile errors reported by CFE using the given [regExp] and adds
/// them to [errors] as coming from [errorSource].
static void _parseCfeErrors(ErrorSource errorSource, RegExp regExp,
- String stdout, List<StaticError> errors) {
+ String stdout, List<StaticError> errors,
+ [List<StaticError> warnings]) {
+ StaticError previousError;
for (var match in regExp.allMatches(stdout)) {
var line = int.parse(match.group(2));
var column = int.parse(match.group(3));
- var message = match.group(4);
- errors.add(StaticError(errorSource, message, line: line, column: column));
+ var severity = match.group(4);
+ var message = match.group(5);
+
+ var error = StaticError(
+ severity == "Context" ? ErrorSource.context : errorSource, message,
+ line: line, column: column);
+
+ if (severity == "Context") {
+ // Attach context messages to the preceding error/warning.
+ if (previousError == null) {
+ DebugLogger.error("Got context message in CFE output before "
+ "error to attach it to.");
+ } else {
+ previousError.contextMessages.add(error);
+ }
+ } else {
+ if (severity == "Error") {
+ errors.add(error);
+ } else {
+ warnings.add(error);
+ }
+ previousError = error;
+ }
}
}
diff --git a/pkg/test_runner/lib/src/static_error.dart b/pkg/test_runner/lib/src/static_error.dart
index d8ede3a..a24aad6 100644
--- a/pkg/test_runner/lib/src/static_error.dart
+++ b/pkg/test_runner/lib/src/static_error.dart
@@ -11,6 +11,9 @@
static const cfe = ErrorSource._("CFE");
static const web = ErrorSource._("web");
+ /// Pseudo-front end for context messages.
+ static const context = ErrorSource._("context");
+
/// All of the supported front ends.
///
/// The order is significant here. In static error tests, error expectations
@@ -24,6 +27,8 @@
if (source.marker == name) return source;
}
+ if (name == "context") return context;
+
return null;
}
@@ -113,6 +118,9 @@
///
/// * Otherwise, any remaining expected errors are considered missing
/// errors and remaining actual errors are considered unexpected.
+ ///
+ /// Also describes any mismatches between the context messages in the expected
+ /// and actual errors.
static String validateExpectations(Iterable<StaticError> expectedErrors,
Iterable<StaticError> actualErrors) {
var expected = expectedErrors.toList();
@@ -122,16 +130,20 @@
expected.sort();
actual.sort();
- // Discard matched errors.
+ var buffer = StringBuffer();
+
+ // Pair up errors by location and message.
for (var i = 0; i < expected.length; i++) {
var matchedExpected = false;
for (var j = 0; j < actual.length; j++) {
if (actual[j] == null) continue;
- if (expected[i]._matchLocation(actual[j]) == null &&
- (expected[i].message == actual[j].message ||
- !expected[i].isSpecified)) {
+ if (expected[i]._matchMessage(actual[j]) &&
+ expected[i]._matchLocation(actual[j])) {
+ // Report any mismatches in the context messages.
+ expected[i]._validateContext(actual[j], buffer);
+
actual[j] = null;
matchedExpected = true;
@@ -147,12 +159,13 @@
expected.removeWhere((error) => error == null);
actual.removeWhere((error) => error == null);
- // If everything matched, we're done.
- if (expected.isEmpty && actual.isEmpty) return null;
+ // If every error was paired up, and the contexts matched, we're done.
+ if (expected.isEmpty && actual.isEmpty && buffer.isEmpty) return null;
- var buffer = StringBuffer();
+ void fail(StaticError error, String label, String contextLabel,
+ [String secondary]) {
+ if (error.isContext) label = contextLabel;
- void fail(StaticError error, String label, [String secondary]) {
if (error.isSpecified) {
buffer.writeln("- $label ${error.location}: ${error.message}");
} else {
@@ -172,8 +185,10 @@
if (actual[j] == null) continue;
if (expected[i].message == actual[j].message) {
- fail(expected[i], "Wrong error location",
- expected[i]._matchLocation(actual[j]));
+ fail(expected[i], "Wrong error location", "Wrong context location",
+ expected[i]._locationError(actual[j]));
+ // Report any mismatches in the context messages.
+ expected[i]._validateContext(actual[j], buffer);
// Only report this mismatch once.
expected[i] = null;
@@ -189,9 +204,11 @@
for (var j = 0; j < actual.length; j++) {
if (actual[j] == null) continue;
- if (expected[i]._matchLocation(actual[j]) == null) {
- fail(actual[j], "Wrong message at",
+ if (expected[i]._matchLocation(actual[j])) {
+ fail(actual[j], "Wrong message at", "Wrong context message at",
"Expected: ${expected[i].message}");
+ // Report any mismatches in the context messages.
+ expected[i]._validateContext(actual[j], buffer);
// Only report this mismatch once.
expected[i] = null;
@@ -204,13 +221,14 @@
// Any remaining expected errors are missing.
for (var i = 0; i < expected.length; i++) {
if (expected[i] == null) continue;
- fail(expected[i], "Missing expected error at");
+ fail(expected[i], "Missing expected error at",
+ "Missing expected context message at");
}
// Any remaining actual errors are unexpected.
for (var j = 0; j < actual.length; j++) {
if (actual[j] == null) continue;
- fail(actual[j], "Unexpected error at");
+ fail(actual[j], "Unexpected error at", "Unexpected context message at");
}
return buffer.toString().trimRight();
@@ -232,6 +250,9 @@
final String message;
+ /// Additional context messages associated with this error.
+ final List<StaticError> contextMessages = [];
+
/// The zero-based numbers of the lines in the [TestFile] containing comments
/// that were parsed to produce this error.
///
@@ -271,6 +292,9 @@
/// met.
bool get isSpecified => message != _unspecified;
+ /// Whether this is a context message instead of an error.
+ bool get isContext => source == ErrorSource.context;
+
/// Whether this error is only considered a warning on all front ends that
/// report it.
bool get isWarning {
@@ -290,7 +314,21 @@
throw FallThroughError();
}
- String toString() => "[${source.marker} $location] $message";
+ String toString() {
+ var buffer = StringBuffer("StaticError(");
+ buffer.write("line: $line, column: $column");
+ if (length != null) buffer.write(", length: $length");
+ buffer.write(", message: '$message'");
+
+ if (contextMessages.isNotEmpty) {
+ buffer.write(", context: [ ");
+ buffer.writeAll(contextMessages, ", ");
+ buffer.write(" ]");
+ }
+
+ buffer.write(")");
+ return buffer.toString();
+ }
/// Orders errors primarily by location, then by other fields if needed.
@override
@@ -311,7 +349,20 @@
}
@override
- bool operator ==(other) => other is StaticError && compareTo(other) == 0;
+ bool operator ==(other) {
+ if (other is StaticError) {
+ if (compareTo(other) != 0) return false;
+
+ if (contextMessages.length != other.contextMessages.length) return false;
+ for (var i = 0; i < contextMessages.length; i++) {
+ if (contextMessages[i] != other.contextMessages[i]) return false;
+ }
+
+ return true;
+ }
+
+ return false;
+ }
@override
int get hashCode =>
@@ -321,11 +372,31 @@
11 * source.hashCode +
13 * message.hashCode;
- /// Returns a string describing how this error's expected location differs
- /// from [actual], or `null` if [actual]'s location matches this one.
+ /// Returns true if [actual]'s message matches this one.
+ ///
+ /// Takes unspecified errors into account.
+ bool _matchMessage(StaticError actual) {
+ return !isSpecified || message == actual.message;
+ }
+
+ /// Returns true if [actual]'s location matches this one.
///
/// Takes into account unspecified errors and errors without lengths.
- String _matchLocation(StaticError actual) {
+ bool _matchLocation(StaticError actual) {
+ if (line != actual.line) return false;
+
+ // Ignore column and length for unspecified errors.
+ if (isSpecified) {
+ if (column != actual.column) return false;
+ if (actual.length != null && length != actual.length) return false;
+ }
+
+ return true;
+ }
+
+ /// Returns a string describing how this error's expected location differs
+ /// from [actual].
+ String _locationError(StaticError actual) {
var expectedMismatches = <String>[];
var actualMismatches = <String>[];
@@ -347,15 +418,28 @@
}
}
- if (expectedMismatches.isEmpty) {
- // Everything matches.
- return null;
- }
+ // Should only call this when the locations don't match.
+ assert(expectedMismatches.isNotEmpty);
var expectedList = expectedMismatches.join(", ");
var actualList = actualMismatches.join(", ");
return "Expected $expectedList but was $actualList.";
}
+
+ /// Validates that this expected error's context messages match [actual]'s.
+ ///
+ /// Writes any mismatch errors to [buffer].
+ void _validateContext(StaticError actual, StringBuffer buffer) {
+ // If the expected error has no context, then ignore actual context
+ // messages.
+ if (contextMessages.isEmpty) return;
+
+ var result = validateExpectations(contextMessages, actual.contextMessages);
+ if (result != null) {
+ buffer.writeln(result);
+ buffer.writeln();
+ }
+ }
}
class _ErrorExpectationParser {
@@ -382,7 +466,10 @@
RegExp(r"^\s*//\s*\[\s*error line\s+(\d+)\s*,\s*column\s+(\d+)\s*\]\s*$");
/// Matches the beginning of an error message, like `// [analyzer]`.
- static final _errorMessageRegExp = RegExp(r"^\s*// \[(\w+)\]\s*(.*)");
+ ///
+ /// May have an optional number like `// [cfe 32]`.
+ static final _errorMessageRegExp =
+ RegExp(r"^\s*// \[(\w+)(\s+\d+)?\]\s*(.*)");
/// An analyzer error code is a dotted identifier or the magic string
/// "unspecified".
@@ -394,6 +481,17 @@
final List<String> _lines;
final List<StaticError> _errors = [];
+
+ /// The parsed context messages.
+ ///
+ /// Once parsing is done, these are added to the errors that own them.
+ final List<StaticError> _contextMessages = [];
+
+ /// For errors that have a number associated with them, tracks that number.
+ ///
+ /// These are used after parsing to attach context messages to their errors.
+ final Map<StaticError, int> _errorNumbers = {};
+
int _currentLine = 0;
// One-based index of the last line that wasn't part of an error expectation.
@@ -402,6 +500,7 @@
_ErrorExpectationParser(String source) : _lines = source.split("\n");
List<StaticError> _parse() {
+ // Read all the lines.
while (_canPeek(0)) {
var sourceLine = _peek(0);
@@ -441,6 +540,7 @@
_advance();
}
+ _attachContext();
return _errors;
}
@@ -454,11 +554,16 @@
var match = _errorMessageRegExp.firstMatch(_peek(1));
if (match == null) break;
+ var number = match.group(2) != null ? int.parse(match.group(2)) : null;
+
var sourceName = match.group(1);
var source = ErrorSource.find(sourceName);
if (source == null) _fail("Unknown front end '[$sourceName]'.");
+ if (source == ErrorSource.context && number == null) {
+ _fail("Context messages must have an error number.");
+ }
- var message = match.group(2);
+ var message = match.group(3);
_advance();
var sourceLines = {locationLine, _currentLine};
@@ -498,11 +603,32 @@
errorLength = null;
}
- _errors.add(StaticError(source, message,
+ var error = StaticError(source, message,
line: line,
column: column,
length: errorLength,
- sourceLines: sourceLines));
+ sourceLines: sourceLines);
+
+ if (number != null) {
+ // Make sure two errors don't claim the same number.
+ if (source != ErrorSource.context) {
+ var existingError = _errors.firstWhere(
+ (error) => _errorNumbers[error] == number,
+ orElse: () => null);
+ if (existingError != null) {
+ _fail("Already have an error with number $number.");
+ }
+ }
+
+ _errorNumbers[error] = number;
+ }
+
+ if (source == ErrorSource.context) {
+ _contextMessages.add(error);
+ } else {
+ _errors.add(error);
+ }
+
parsedError = true;
}
@@ -511,6 +637,38 @@
}
}
+ /// Attach context messages to their errors and validate that everything lines
+ /// up.
+ void _attachContext() {
+ for (var contextMessage in _contextMessages) {
+ var number = _errorNumbers[contextMessage];
+
+ var error = _errors.firstWhere((error) => _errorNumbers[error] == number,
+ orElse: () => null);
+ if (error == null) {
+ throw FormatException("No error with number $number for context "
+ "message '${contextMessage.message}'.");
+ }
+
+ error.contextMessages.add(contextMessage);
+ }
+
+ // Make sure every numbered error does have some context, otherwise the
+ // number is pointless.
+ for (var error in _errors) {
+ var number = _errorNumbers[error];
+ if (number == null) continue;
+
+ var context = _contextMessages.firstWhere(
+ (context) => _errorNumbers[context] == number,
+ orElse: () => null);
+ if (context == null) {
+ throw FormatException("Missing context for numbered error $number "
+ "'${error.message}'.");
+ }
+ }
+ }
+
bool _canPeek(int offset) => _currentLine + offset < _lines.length;
void _advance() {
diff --git a/pkg/test_runner/lib/src/testing_servers.dart b/pkg/test_runner/lib/src/testing_servers.dart
index 5e9df68..bbd5cfd 100644
--- a/pkg/test_runner/lib/src/testing_servers.dart
+++ b/pkg/test_runner/lib/src/testing_servers.dart
@@ -343,7 +343,7 @@
]) {
response.headers.set(header, content_header_value);
}
- if (const ["safari"].contains(runtime)) {
+ if (runtime == Runtime.safari) {
response.headers.set("X-WebKit-CSP", content_header_value);
}
}
diff --git a/pkg/test_runner/lib/src/update_errors.dart b/pkg/test_runner/lib/src/update_errors.dart
index 5003a52..0023b02 100644
--- a/pkg/test_runner/lib/src/update_errors.dart
+++ b/pkg/test_runner/lib/src/update_errors.dart
@@ -13,9 +13,10 @@
/// for the given [errors].
///
/// If [remove] is not `null`, then only removes existing errors for the given
-/// sources.
+/// sources. If [includeContext] is `true`, then includes context messages in
+/// the output. Otherwise discards them.
String updateErrorExpectations(String source, List<StaticError> errors,
- {Set<ErrorSource> remove}) {
+ {Set<ErrorSource> remove, bool includeContext = false}) {
remove ??= {};
// Split the existing errors into kept and deleted lists.
@@ -39,15 +40,20 @@
// Remove all existing marker comments in the file, even for errors we are
// preserving. We will regenerate marker comments for those errors too so
// they can properly share location comments with new errors if needed.
+ void removeLine(int line) {
+ if (lines[line] == null) return;
+
+ indentation[line] = _countIndentation(lines[line]);
+
+ // Null the line instead of removing it so that line numbers in the
+ // reported errors are still correct.
+ lines[line] = null;
+ }
+
for (var error in existingErrors) {
- for (var line in error.sourceLines) {
- if (lines[line] == null) continue;
-
- indentation[line] = _countIndentation(lines[line]);
-
- // Null the line instead of removing it so that line numbers in the
- // reported errors are still correct.
- lines[line] = null;
+ error.sourceLines.forEach(removeLine);
+ for (var contextMessage in error.contextMessages) {
+ contextMessage.sourceLines.forEach(removeLine);
}
}
@@ -59,6 +65,14 @@
for (var error in errors) {
// -1 to translate from one-based to zero-based index.
errorMap.putIfAbsent(error.line - 1, () => []).add(error);
+
+ // Flatten out and include context messages.
+ if (includeContext) {
+ for (var context in error.contextMessages) {
+ // -1 to translate from one-based to zero-based index.
+ errorMap.putIfAbsent(context.line - 1, () => []).add(context);
+ }
+ }
}
// If there are multiple errors on the same line, order them
@@ -67,6 +81,8 @@
errorList.sort();
}
+ var errorNumbers = _numberErrors(errors);
+
// Rebuild the source file a line at a time.
var previousIndent = 0;
var codeLine = 1;
@@ -131,7 +147,12 @@
previousLength = error.length;
var errorLines = error.message.split("\n");
- result.add("$comment [${error.source.marker}] ${errorLines[0]}");
+ var line = "$comment [${error.source.marker}";
+ if (includeContext && errorNumbers.containsKey(error)) {
+ line += " ${errorNumbers[error]}";
+ }
+ line += "] ${errorLines[0]}";
+ result.add(line);
for (var errorLine in errorLines.skip(1)) {
result.add("$comment $errorLine");
}
@@ -150,6 +171,25 @@
return result.join("\n");
}
+/// Assigns unique numbers to all [errors] that have context messages, as well
+/// as their context messages.
+Map<StaticError, int> _numberErrors(List<StaticError> errors) {
+ var result = <StaticError, int>{};
+ var number = 1;
+ for (var error in errors) {
+ if (error.contextMessages.isEmpty) continue;
+
+ result[error] = number;
+ for (var context in error.contextMessages) {
+ result[context] = number;
+ }
+
+ number++;
+ }
+
+ return result;
+}
+
/// Returns the number of characters of leading spaces in [line].
int _countIndentation(String line) {
var match = _indentationRegExp.firstMatch(line);
diff --git a/pkg/test_runner/test/static_error_test.dart b/pkg/test_runner/test/static_error_test.dart
index 891db234..285e41d 100644
--- a/pkg/test_runner/test/static_error_test.dart
+++ b/pkg/test_runner/test/static_error_test.dart
@@ -12,7 +12,6 @@
testProperties();
testIsWarning();
testCompareTo();
- // testDescribeDifferences();
testValidate();
}
@@ -223,6 +222,105 @@
makeError(line: 1, column: 2, length: 3, webError: "Web 2."),
makeError(line: 1, column: 3, length: 3, webError: "Web 3."),
], null);
+
+ // If expectation has context, actual must match it.
+ expectValidate([
+ makeError(line: 1, column: 2, length: 3, cfeError: "Error.", context: [
+ makeError(line: 4, column: 5, length: 6, contextError: "Context A."),
+ makeError(line: 7, column: 8, length: 9, contextError: "Context B."),
+ ]),
+ ], [
+ makeError(line: 1, column: 2, length: 3, cfeError: "Error.", context: [
+ makeError(line: 4, column: 5, length: 6, contextError: "Context A."),
+ makeError(line: 7, column: 8, length: 9, contextError: "Context B."),
+ ]),
+ ], null);
+
+ // Actual context is different.
+ expectValidate([
+ makeError(line: 1, column: 2, length: 3, cfeError: "Error.", context: [
+ makeError(line: 4, column: 5, length: 6, contextError: "Context A."),
+ ]),
+ ], [
+ makeError(line: 1, column: 2, length: 3, cfeError: "Error.", context: [
+ makeError(line: 4, column: 5, length: 6, contextError: "Context Z."),
+ ]),
+ ], """
+- Wrong context message at line 4, column 5, length 6: Context Z.
+ Expected: Context A.""");
+
+ // Missing some actual context.
+ expectValidate([
+ makeError(line: 1, column: 2, length: 3, cfeError: "Error.", context: [
+ makeError(line: 4, column: 5, length: 6, contextError: "Context A."),
+ makeError(line: 7, column: 8, length: 9, contextError: "Context B."),
+ ]),
+ ], [
+ makeError(line: 1, column: 2, length: 3, cfeError: "Error.", context: [
+ makeError(line: 7, column: 8, length: 9, contextError: "Context B."),
+ ]),
+ ], """
+- Missing expected context message at line 4, column 5, length 6: Context A.""");
+
+ // Missing all actual context.
+ expectValidate([
+ makeError(line: 1, column: 2, length: 3, cfeError: "Error.", context: [
+ makeError(line: 4, column: 5, length: 6, contextError: "Context A."),
+ makeError(line: 7, column: 8, length: 9, contextError: "Context B."),
+ ]),
+ ], [
+ makeError(line: 1, column: 2, length: 3, cfeError: "Error."),
+ ], """
+- Missing expected context message at line 4, column 5, length 6: Context A.
+
+- Missing expected context message at line 7, column 8, length 9: Context B.""");
+
+ // Unexpected extra actual context.
+ expectValidate([
+ makeError(line: 1, column: 2, length: 3, cfeError: "Error.", context: [
+ makeError(line: 4, column: 5, length: 6, contextError: "Context A."),
+ ]),
+ ], [
+ makeError(line: 1, column: 2, length: 3, cfeError: "Error.", context: [
+ makeError(line: 4, column: 5, length: 6, contextError: "Context A."),
+ makeError(line: 7, column: 8, length: 9, contextError: "Context B."),
+ ]),
+ ], """
+- Unexpected context message at line 7, column 8, length 9: Context B.""");
+
+ // Actual context owned by wrong error.
+ // TODO(rnystrom): This error is pretty confusing. Ideally we would detect
+ // this case specifically and give better guidance.
+ expectValidate([
+ makeError(line: 1, column: 2, length: 3, cfeError: "Error A.", context: [
+ makeError(line: 4, column: 5, length: 6, contextError: "Context A."),
+ makeError(line: 33, column: 5, length: 6, contextError: "Context."),
+ ]),
+ makeError(line: 10, column: 2, length: 3, cfeError: "Error B.", context: [
+ makeError(line: 11, column: 5, length: 6, contextError: "Context B."),
+ ]),
+ ], [
+ makeError(line: 1, column: 2, length: 3, cfeError: "Error A.", context: [
+ makeError(line: 4, column: 5, length: 6, contextError: "Context A."),
+ ]),
+ makeError(line: 10, column: 2, length: 3, cfeError: "Error B.", context: [
+ makeError(line: 11, column: 5, length: 6, contextError: "Context B."),
+ makeError(line: 33, column: 5, length: 6, contextError: "Context."),
+ ]),
+ ], """
+- Missing expected context message at line 33, column 5, length 6: Context.
+
+- Unexpected context message at line 33, column 5, length 6: Context.""");
+
+ // If expectation has no context at all, then ignore actual context.
+ expectValidate([
+ makeError(line: 1, column: 2, length: 3, cfeError: "Error."),
+ ], [
+ makeError(line: 1, column: 2, length: 3, cfeError: "Error.", context: [
+ makeError(line: 4, column: 5, length: 6, contextError: "Context A."),
+ makeError(line: 7, column: 8, length: 9, contextError: "Context B."),
+ ]),
+ ], null);
}
void expectValidate(List<StaticError> expected, List<StaticError> actual,
diff --git a/pkg/test_runner/test/test_file_test.dart b/pkg/test_runner/test/test_file_test.dart
index 1e793ef..8e5ddc1 100644
--- a/pkg/test_runner/test/test_file_test.dart
+++ b/pkg/test_runner/test/test_file_test.dart
@@ -28,6 +28,7 @@
testParseMultitest();
testParseErrorFlags();
testParseErrorExpectations();
+ testParseContextMessages();
testIsRuntimeTest();
testName();
testMultitest();
@@ -556,6 +557,145 @@
]);
}
+void testParseContextMessages() {
+ // Multiple messages.
+ expectParseErrorExpectations("""
+var string = "str";
+/\/ ^^^^^^
+/\/ [context 1] Analyzer context before.
+/\/ [context 2] CFE context before.
+
+int j = string;
+/\/ ^^^^^^
+/\/ [analyzer 1] Error.BAD
+/\/ [cfe 2] Error message.
+
+var string = "str";
+/\/ ^^^
+/\/ [context 2] CFE context after.
+
+var string = "str";
+/\/ ^^^
+/\/ [context 1] Analyzer context after.
+""", [
+ makeError(
+ line: 6,
+ column: 9,
+ length: 6,
+ analyzerError: "Error.BAD",
+ context: [
+ makeError(
+ line: 1,
+ column: 5,
+ length: 6,
+ analyzerError: "Analyzer context before."),
+ makeError(
+ line: 15,
+ column: 15,
+ length: 3,
+ analyzerError: "Analyzer context after.")
+ ]),
+ makeError(
+ line: 6,
+ column: 9,
+ length: 6,
+ cfeError: "Error message.",
+ context: [
+ makeError(
+ line: 1,
+ column: 5,
+ length: 6,
+ analyzerError: "CFE context before."),
+ makeError(
+ line: 11,
+ column: 15,
+ length: 3,
+ analyzerError: "CFE context after.")
+ ]),
+ ]);
+
+ // Context before error.
+ expectParseErrorExpectations("""
+var string = "str";
+/\/ ^^^^^^
+/\/ [context 1] Context.
+
+int j = string;
+/\/ ^^^^^^
+/\/ [analyzer 1] Error.BAD
+""", [
+ makeError(
+ line: 5,
+ column: 9,
+ length: 6,
+ analyzerError: "Error.BAD",
+ context: [
+ makeError(line: 1, column: 5, length: 6, analyzerError: "Context.")
+ ]),
+ ]);
+
+ // Context after error.
+ expectParseErrorExpectations("""
+int j = string;
+/\/ ^^^^^^
+/\/ [analyzer 1] Error.BAD
+
+var string = "str";
+/\/ ^^^^^^
+/\/ [context 1] Context.
+""", [
+ makeError(
+ line: 1,
+ column: 9,
+ length: 6,
+ analyzerError: "Error.BAD",
+ context: [
+ makeError(line: 5, column: 5, length: 6, analyzerError: "Context.")
+ ]),
+ ]);
+
+ // Context must have a number.
+ expectFormatError("""
+int i = "s";
+/\/ ^^^
+/\/ [context] No number.
+
+int i = "s";
+/\/ ^^^
+/\/ [cfe 1] Error.
+""");
+
+ // Context number must match an error.
+ expectFormatError("""
+int i = "s";
+/\/ ^^^
+/\/ [context 2] Wrong number.
+
+int i = "s";
+/\/ ^^^
+/\/ [cfe 1] Error.
+""");
+
+ // Two errors with same number.
+ expectFormatError("""
+int i = "s";
+/\/ ^^^
+/\/ [context 1] Context.
+
+int i = "s";
+/\/ ^^^
+/\/ [cfe 1] Error.
+/\/ [analyzer 1] Error.CODE
+""");
+
+ // Numbered error with no context.
+ expectFormatError("""
+int i = "s";
+/\/ ^^^
+/\/ [cfe 1] Error.
+""");
+}
+
void testIsRuntimeTest() {
// No static errors at all.
var file = parseTestFile("");
diff --git a/pkg/test_runner/test/update_errors_test.dart b/pkg/test_runner/test/update_errors_test.dart
index 1d459d4..afe5db9 100644
--- a/pkg/test_runner/test/update_errors_test.dart
+++ b/pkg/test_runner/test/update_errors_test.dart
@@ -403,9 +403,192 @@
// [error line 1, column 1, length 0]
// [cfe] Foo""");
+ contextMessages();
regression();
}
+void contextMessages() {
+ // Inserts context messages.
+ expectUpdate(
+ """
+int i = "bad";
+
+int another = "wrong";
+
+int third = "boo";
+""",
+ errors: [
+ makeError(
+ line: 3,
+ column: 15,
+ length: 7,
+ analyzerError: "some.error",
+ context: [
+ makeError(
+ line: 1,
+ column: 9,
+ length: 5,
+ contextError: "Analyzer context."),
+ makeError(
+ line: 5,
+ column: 13,
+ length: 5,
+ contextError: "More context."),
+ ]),
+ makeError(
+ line: 3,
+ column: 15,
+ length: 7,
+ cfeError: "CFE error.",
+ context: [
+ makeError(
+ line: 1, column: 9, length: 5, contextError: "CFE context."),
+ ]),
+ ],
+ remove: {ErrorSource.analyzer},
+ includeContext: true,
+ expected: """
+int i = "bad";
+/\/ ^^^^^
+/\/ [context 1] Analyzer context.
+/\/ [context 2] CFE context.
+
+int another = "wrong";
+/\/ ^^^^^^^
+/\/ [analyzer 1] some.error
+/\/ [cfe 2] CFE error.
+
+int third = "boo";
+/\/ ^^^^^
+/\/ [context 1] More context.
+""");
+
+ // Removes context messages for removed errors.
+ expectUpdate(
+ """
+int i = "bad";
+/\/ ^^^^^
+/\/ [context 1] Analyzer context.
+/\/ [context 2] CFE context.
+
+int another = "wrong";
+/\/ ^^^^^^^
+/\/ [analyzer 1] some.error
+/\/ [cfe 2] CFE error.
+
+int third = "boo";
+/\/ ^^^^^
+/\/ [context 1] More context.
+""",
+ remove: {ErrorSource.analyzer},
+ includeContext: true,
+ expected: """
+int i = "bad";
+/\/ ^^^^^
+/\/ [context 1] CFE context.
+
+int another = "wrong";
+/\/ ^^^^^^^
+/\/ [cfe 1] CFE error.
+
+int third = "boo";
+""");
+
+ // Discards context messages when not told to include them.
+ expectUpdate(
+ """
+int i = "bad";
+
+int another = "wrong";
+
+int third = "boo";
+""",
+ errors: [
+ makeError(
+ line: 3,
+ column: 15,
+ length: 7,
+ analyzerError: "some.error",
+ context: [
+ makeError(
+ line: 1,
+ column: 9,
+ length: 5,
+ contextError: "Analyzer context."),
+ makeError(
+ line: 5,
+ column: 13,
+ length: 5,
+ contextError: "More context."),
+ ]),
+ makeError(
+ line: 3,
+ column: 15,
+ length: 7,
+ cfeError: "CFE error.",
+ context: [
+ makeError(
+ line: 1, column: 9, length: 5, contextError: "CFE context."),
+ ]),
+ ],
+ includeContext: false,
+ expected: """
+int i = "bad";
+
+int another = "wrong";
+/\/ ^^^^^^^
+/\/ [analyzer] some.error
+/\/ [cfe] CFE error.
+
+int third = "boo";
+""");
+
+ // Discards existing context messages when not told to include them.
+ expectUpdate(
+ """
+int i = "bad";
+/\/ ^^^^^
+/\/ [context 1] CFE context.
+
+int another = "wrong";
+/\/ ^^^^^^^
+/\/ [cfe 1] CFE error.
+
+int third = "boo";
+""",
+ errors: [
+ makeError(
+ line: 5,
+ column: 15,
+ length: 7,
+ analyzerError: "some.error",
+ context: [
+ makeError(
+ line: 1,
+ column: 9,
+ length: 5,
+ contextError: "Analyzer context."),
+ makeError(
+ line: 7,
+ column: 13,
+ length: 5,
+ contextError: "More context."),
+ ]),
+ ],
+ remove: {ErrorSource.analyzer},
+ includeContext: false,
+ expected: """
+int i = "bad";
+
+int another = "wrong";
+/\/ ^^^^^^^
+/\/ [analyzer] some.error
+/\/ [cfe] CFE error.
+
+int third = "boo";
+""");
+}
+
void regression() {
// https://github.com/dart-lang/sdk/issues/37990.
expectUpdate("""
@@ -462,11 +645,16 @@
}
void expectUpdate(String original,
- {List<StaticError> errors, Set<ErrorSource> remove, String expected}) {
+ {List<StaticError> errors,
+ Set<ErrorSource> remove,
+ bool includeContext,
+ String expected}) {
errors ??= const [];
remove ??= ErrorSource.all.toSet();
+ includeContext ??= false;
- var actual = updateErrorExpectations(original, errors, remove: remove);
+ var actual = updateErrorExpectations(original, errors,
+ remove: remove, includeContext: includeContext);
if (actual != expected) {
// Not using Expect.equals() because the diffs it shows aren't helpful for
// strings this large.
diff --git a/pkg/test_runner/test/utils.dart b/pkg/test_runner/test/utils.dart
index 2a9152d..e0d8f07 100644
--- a/pkg/test_runner/test/utils.dart
+++ b/pkg/test_runner/test/utils.dart
@@ -28,27 +28,44 @@
/// Creates a [StaticError].
///
-/// Only one of [analyzerError], [cfeError], or [webError] may be passed.
+/// Only one of [analyzerError], [cfeError], [webError], or [contextError] may
+/// be passed.
StaticError makeError(
{int line = 1,
int column = 2,
int length,
String analyzerError,
String cfeError,
- String webError}) {
+ String webError,
+ String contextError,
+ List<StaticError> context}) {
+ ErrorSource source;
+ String message;
if (analyzerError != null) {
- assert(cfeError == null && webError == null);
- return StaticError(ErrorSource.analyzer, analyzerError,
- line: line, column: column, length: length);
+ assert(cfeError == null);
+ assert(webError == null);
+ assert(contextError == null);
+ source = ErrorSource.analyzer;
+ message = analyzerError;
} else if (cfeError != null) {
assert(webError == null);
- return StaticError(ErrorSource.cfe, cfeError,
- line: line, column: column, length: length);
+ assert(contextError == null);
+ source = ErrorSource.cfe;
+ message = cfeError;
+ } else if (webError != null) {
+ assert(contextError == null);
+ source = ErrorSource.web;
+ message = webError;
} else {
- assert(webError != null);
- return StaticError(ErrorSource.web, webError,
- line: line, column: column, length: length);
+ assert(contextError != null);
+ source = ErrorSource.context;
+ message = contextError;
}
+
+ var error =
+ StaticError(source, message, line: line, column: column, length: length);
+ if (context != null) error.contextMessages.addAll(context);
+ return error;
}
class _MockTestSuite extends StandardTestSuite {
diff --git a/pkg/test_runner/tool/update_static_error_tests.dart b/pkg/test_runner/tool/update_static_error_tests.dart
index 96e521e..ed9bad5 100644
--- a/pkg/test_runner/tool/update_static_error_tests.dart
+++ b/pkg/test_runner/tool/update_static_error_tests.dart
@@ -37,6 +37,8 @@
abbr: "n",
help: "Print result but do not overwrite any files.",
negatable: false);
+ parser.addFlag("context",
+ abbr: "c", help: "Include context messages in output.");
parser.addSeparator("What operations to perform:");
parser.addFlag("remove-all",
@@ -61,8 +63,6 @@
help: "Update error expectations for given front ends.",
allowed: sources);
- parser.addSeparator("Other flags:");
-
var results = parser.parse(args);
if (results["help"] as bool) {
@@ -76,6 +76,8 @@
var dryRun = results["dry-run"] as bool;
+ var includeContext = results["context"] as bool;
+
var removeSources = <ErrorSource>{};
var insertSources = <ErrorSource>{};
@@ -132,7 +134,10 @@
if (entry is pkg_file.File) {
await _processFile(entry,
- dryRun: dryRun, remove: removeSources, insert: insertSources);
+ dryRun: dryRun,
+ includeContext: includeContext,
+ remove: removeSources,
+ insert: insertSources);
}
}
}
@@ -146,7 +151,10 @@
}
Future<void> _processFile(File file,
- {bool dryRun, Set<ErrorSource> remove, Set<ErrorSource> insert}) async {
+ {bool dryRun,
+ bool includeContext,
+ Set<ErrorSource> remove,
+ Set<ErrorSource> insert}) async {
stdout.write("${file.path}...");
var source = file.readAsStringSync();
var testFile = TestFile.parse(Path("."), file.absolute.path, source);
@@ -203,7 +211,8 @@
}
}
- var result = updateErrorExpectations(source, errors, remove: remove);
+ var result = updateErrorExpectations(source, errors,
+ remove: remove, includeContext: includeContext);
stdout.writeln("\r${file.path} (Updated with ${errors.length} errors)");
diff --git a/tools/VERSION b/tools/VERSION
index 2715580..705ebe0 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 13
PATCH 0
-PRERELEASE 181
+PRERELEASE 182
PRERELEASE_PATCH 0
\ No newline at end of file