[analyzer] Make constant assertion errors more helpful and specific.
If a condition is valid, but fails in an assert, make sure we report
that and a pseudo trace with context messages for the user to link
back to the problem.
This CL also starts building the stack trace when we call a super constructor for more detailed errors.
Bug: https://github.com/dart-lang/sdk/issues/36526
Change-Id: Ib9bc9841256644d2380d3f806e3bc3c9d84b37cf
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/316625
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Kallen Tu <kallentu@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
index 273ae02..3210c43 100644
--- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
+++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
@@ -384,6 +384,8 @@
status: needsFix
notes: |-
Remove the `deferred` keyword from the import.
+CompileTimeErrorCode.CONST_EVAL_ASSERTION_FAILURE:
+ status: needsEvaluation
CompileTimeErrorCode.CONST_EVAL_EXTENSION_METHOD:
status: needsEvaluation
CompileTimeErrorCode.CONST_EVAL_FOR_ELEMENT:
diff --git a/pkg/analyzer/lib/src/dart/constant/evaluation.dart b/pkg/analyzer/lib/src/dart/constant/evaluation.dart
index 0a480ee..6963067 100644
--- a/pkg/analyzer/lib/src/dart/constant/evaluation.dart
+++ b/pkg/analyzer/lib/src/dart/constant/evaluation.dart
@@ -25,9 +25,11 @@
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_algebra.dart';
import 'package:analyzer/src/dart/element/type_system.dart' show TypeSystemImpl;
+import 'package:analyzer/src/diagnostic/diagnostic.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/constant.dart';
import 'package:analyzer/src/generated/engine.dart';
+import 'package:analyzer/src/generated/java_core.dart';
import 'package:analyzer/src/task/api/model.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
import 'package:analyzer/src/utilities/extensions/object.dart';
@@ -2527,12 +2529,25 @@
superName: evaluationResult.superName,
superArguments: evaluationResult.superArguments);
if (error != null) {
- // TODO(kallentu): Report a better error here with context from the other
- // error reported.
+ final formattedMessage =
+ formatList(error.errorCode.problemMessage, error.arguments);
+ final contextMessage = DiagnosticMessageImpl(
+ filePath: _library.source.fullName,
+ length: error.node.length,
+ message: "The exception is '$formattedMessage' and occurs here.",
+ offset: error.node.offset,
+ url: null,
+ );
+
+ // TODO(kallentu): When removing all on-site reporting, move this error
+ // to [_InstanceCreationEvaluator.evaluate] and provide context for all
+ // constructor related errors.
_errorReporter.reportErrorForNode(
- CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION, _errorNode);
- return InvalidConstant(
- _errorNode, CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION);
+ CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION,
+ _errorNode,
+ [],
+ [...error.contextMessages, contextMessage]);
+ return error;
}
return DartObjectImpl(
@@ -2755,12 +2770,12 @@
case DartObjectImpl():
if (!evaluationConstant.isBool ||
evaluationConstant.toBoolValue() == false) {
- // TODO(kallentu): Report a better error here.
+ // TODO(kallentu): Don't report error here.
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION, _errorNode);
return _InitializersEvaluationResult(
- InvalidConstant(_errorNode,
- CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION),
+ InvalidConstant(initializer,
+ CompileTimeErrorCode.CONST_EVAL_ASSERTION_FAILURE),
evaluationIsComplete: true);
}
case InvalidConstant():
@@ -2913,6 +2928,16 @@
case DartObjectImpl():
_fieldMap[GenericState.SUPERCLASS_FIELD] = evaluationResult;
case InvalidConstant():
+ evaluationResult.contextMessages.add(DiagnosticMessageImpl(
+ filePath: _constructor.source.fullName,
+ length: _constructor.nameLength,
+ message:
+ "The evaluated constructor '${superConstructor.displayName}' "
+ "is called by '${_constructor.displayName}' and "
+ "'${_constructor.displayName}' is defined here.",
+ offset: _constructor.nameOffset,
+ url: null,
+ ));
return evaluationResult;
}
}
diff --git a/pkg/analyzer/lib/src/error/codes.g.dart b/pkg/analyzer/lib/src/error/codes.g.dart
index 2c1f843..2bdb58b 100644
--- a/pkg/analyzer/lib/src/error/codes.g.dart
+++ b/pkg/analyzer/lib/src/error/codes.g.dart
@@ -807,6 +807,12 @@
hasPublishedDocs: true,
);
+ static const CompileTimeErrorCode CONST_EVAL_ASSERTION_FAILURE =
+ CompileTimeErrorCode(
+ 'CONST_EVAL_ASSERTION_FAILURE',
+ "The assertion in this constant expression failed.",
+ );
+
static const CompileTimeErrorCode CONST_EVAL_EXTENSION_METHOD =
CompileTimeErrorCode(
'CONST_EVAL_EXTENSION_METHOD',
diff --git a/pkg/analyzer/lib/src/error/error_code_values.g.dart b/pkg/analyzer/lib/src/error/error_code_values.g.dart
index 75a5a61..9da86b3 100644
--- a/pkg/analyzer/lib/src/error/error_code_values.g.dart
+++ b/pkg/analyzer/lib/src/error/error_code_values.g.dart
@@ -110,6 +110,7 @@
CompileTimeErrorCode.CONST_CONSTRUCTOR_WITH_NON_CONST_SUPER,
CompileTimeErrorCode.CONST_CONSTRUCTOR_WITH_NON_FINAL_FIELD,
CompileTimeErrorCode.CONST_DEFERRED_CLASS,
+ CompileTimeErrorCode.CONST_EVAL_ASSERTION_FAILURE,
CompileTimeErrorCode.CONST_EVAL_EXTENSION_METHOD,
CompileTimeErrorCode.CONST_EVAL_FOR_ELEMENT,
CompileTimeErrorCode.CONST_EVAL_METHOD_INVOCATION,
diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml
index 38bd475..5c35cf8 100644
--- a/pkg/analyzer/messages.yaml
+++ b/pkg/analyzer/messages.yaml
@@ -2575,6 +2575,8 @@
const json2 = convert.JsonCodec();
```
+ CONST_EVAL_ASSERTION_FAILURE:
+ problemMessage: "The assertion in this constant expression failed."
CONST_EVAL_EXTENSION_METHOD:
problemMessage: "Extension methods can't be used in constant expressions."
CONST_EVAL_FOR_ELEMENT:
diff --git a/pkg/analyzer/test/src/dart/constant/evaluation_test.dart b/pkg/analyzer/test/src/dart/constant/evaluation_test.dart
index f4a67f3..001170c 100644
--- a/pkg/analyzer/test/src/dart/constant/evaluation_test.dart
+++ b/pkg/analyzer/test/src/dart/constant/evaluation_test.dart
@@ -3447,6 +3447,35 @@
@reflectiveTest
mixin InstanceCreationEvaluatorTestCases on ConstantVisitorTestSupport {
+ test_assertInitializer_indirect() async {
+ await assertErrorsInCode(r'''
+class A {
+ const A(int i)
+ : assert(i == 1); // (2)
+}
+class B extends A {
+ const B(int i) : super(i);
+}
+main() {
+ print(const B(2)); // (1)
+}
+''', [
+ error(
+ CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION,
+ 124,
+ 10,
+ contextMessages: [
+ ExpectedContextMessage('/home/test/lib/test.dart', 84, 1,
+ text:
+ "The evaluated constructor 'A' is called by 'B' and 'B' is defined here."),
+ ExpectedContextMessage('/home/test/lib/test.dart', 31, 14,
+ text:
+ "The exception is 'The assertion in this constant expression failed.' and occurs here."),
+ ],
+ ),
+ ]);
+ }
+
test_assertInitializer_intInDoubleContext_false() async {
await assertErrorsInCode('''
class A {
@@ -3479,7 +3508,19 @@
}
const b = const B();
''', [
- error(CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION, 101, 9),
+ error(
+ CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION,
+ 101,
+ 9,
+ contextMessages: [
+ ExpectedContextMessage('/home/test/lib/test.dart', 74, 1,
+ text:
+ "The evaluated constructor 'A' is called by 'B' and 'B' is defined here."),
+ ExpectedContextMessage('/home/test/lib/test.dart', 23, 19,
+ text:
+ "The exception is 'The assertion in this constant expression failed.' and occurs here."),
+ ],
+ ),
]);
}
diff --git a/pkg/analyzer/test/src/diagnostics/const_eval_throws_exception_test.dart b/pkg/analyzer/test/src/diagnostics/const_eval_throws_exception_test.dart
index dc849a3..9d323df 100644
--- a/pkg/analyzer/test/src/diagnostics/const_eval_throws_exception_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/const_eval_throws_exception_test.dart
@@ -6,6 +6,7 @@
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
+import '../../generated/test_support.dart';
import '../dart/resolution/context_collection_resolution.dart';
main() {
@@ -65,6 +66,33 @@
error(CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION, 11, 3),
]);
}
+
+ test_superConstructor_paramTypeMismatch() async {
+ await assertErrorsInCode(r'''
+class C {
+ final double d;
+ const C(this.d);
+}
+class D extends C {
+ const D(d) : super(d);
+}
+const f = const D('0.0');
+''', [
+ error(
+ CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION,
+ 106,
+ 14,
+ contextMessages: [
+ ExpectedContextMessage('/home/test/lib/test.dart', 90, 1,
+ text:
+ "The exception is 'A value of type 'String' can't be assigned to a parameter of type 'double' in a const constructor.' and occurs here."),
+ ExpectedContextMessage('/home/test/lib/test.dart', 77, 1,
+ text:
+ "The evaluated constructor 'C' is called by 'D' and 'D' is defined here."),
+ ],
+ ),
+ ]);
+ }
}
@reflectiveTest
@@ -80,6 +108,35 @@
]);
}
+ test_assertion_indirect() async {
+ await assertErrorsInCode(r'''
+class A {
+ const A(int i)
+ : assert(i == 1); // (2)
+}
+class B extends A {
+ const B(int i) : super(i);
+}
+main() {
+ print(const B(2)); // (1)
+}
+''', [
+ error(
+ CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION,
+ 124,
+ 10,
+ contextMessages: [
+ ExpectedContextMessage('/home/test/lib/test.dart', 84, 1,
+ text:
+ "The evaluated constructor 'A' is called by 'B' and 'B' is defined here."),
+ ExpectedContextMessage('/home/test/lib/test.dart', 31, 14,
+ text:
+ "The exception is 'The assertion in this constant expression failed.' and occurs here."),
+ ],
+ ),
+ ]);
+ }
+
test_CastError_intToDouble_constructor_importAnalyzedAfter() async {
// See dartbug.com/35993
newFile('$testPackageLibPath/other.dart', '''
@@ -289,21 +346,6 @@
]);
}
- test_superConstructor_paramTypeMismatch() async {
- await assertErrorsInCode(r'''
-class C {
- final double d;
- const C(this.d);
-}
-class D extends C {
- const D(d) : super(d);
-}
-const f = const D('0.0');
-''', [
- error(CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION, 106, 14),
- ]);
- }
-
test_symbolConstructor_nonStringArgument() async {
await assertErrorsInCode(r'''
var s2 = const Symbol(3);