[analyzer] Provide better errors for invalid .length access in const expressions.
Add a new error message `CONST_EVAL_PROPERTY_ACCESS` for when we try to get 'length' on anything that's not a `String`.
This adds more specific errors for string length that would've went to the catch-all error at the end of `_getConstantValue` before. It should be more clear now what the error is.
Fixes https://github.com/dart-lang/sdk/issues/47273, https://github.com/dart-lang/sdk/issues/52833
Bug: https://github.com/dart-lang/sdk/issues/52833, https://github.com/dart-lang/sdk/issues/47273
Change-Id: I1f5daed82be00c6a8ecd43384d7ff9e59219cbb4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/319160
Commit-Queue: Kallen Tu <kallentu@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@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 bea1e01..9313b7c 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
@@ -394,6 +394,8 @@
status: needsEvaluation
CompileTimeErrorCode.CONST_EVAL_METHOD_INVOCATION:
status: needsEvaluation
+CompileTimeErrorCode.CONST_EVAL_PROPERTY_ACCESS:
+ status: needsEvaluation
CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION:
status: noFix
CompileTimeErrorCode.CONST_EVAL_THROWS_IDBZE:
diff --git a/pkg/analyzer/lib/src/dart/constant/evaluation.dart b/pkg/analyzer/lib/src/dart/constant/evaluation.dart
index d468ac1..21e964c 100644
--- a/pkg/analyzer/lib/src/dart/constant/evaluation.dart
+++ b/pkg/analyzer/lib/src/dart/constant/evaluation.dart
@@ -1050,8 +1050,8 @@
@override
Constant visitPrefixedIdentifier(PrefixedIdentifier node) {
- SimpleIdentifier prefixNode = node.prefix;
- var prefixElement = prefixNode.staticElement;
+ final prefixNode = node.prefix;
+ final prefixElement = prefixNode.staticElement;
// importPrefix.CONST
if (prefixElement is PrefixElement) {
@@ -1060,15 +1060,17 @@
InvalidConstant(node, CompileTimeErrorCode.INVALID_CONSTANT);
}
} else if (prefixElement is! ExtensionElement) {
- var prefixResult = _getConstant(prefixNode);
+ final prefixResult = _getConstant(prefixNode);
if (prefixResult is! DartObjectImpl) {
return prefixResult;
}
// String.length
if (prefixElement is! InterfaceElement) {
- if (_isStringLength(prefixResult, node.identifier)) {
- return prefixResult.stringLength(typeSystem);
+ final stringLengthResult =
+ _evaluateStringLength(prefixResult, node.identifier, node);
+ if (stringLengthResult != null) {
+ return stringLengthResult;
}
}
}
@@ -1113,12 +1115,15 @@
Constant visitPropertyAccess(PropertyAccess node) {
var target = node.target;
if (target != null) {
- var prefixResult = _getConstant(target);
+ final prefixResult = _getConstant(target);
if (prefixResult is! DartObjectImpl) {
return prefixResult;
}
- if (_isStringLength(prefixResult, node.propertyName)) {
- return prefixResult.stringLength(typeSystem);
+
+ final stringLengthResult =
+ _evaluateStringLength(prefixResult, node.propertyName, node);
+ if (stringLengthResult != null) {
+ return stringLengthResult;
}
}
return _getConstantValue(
@@ -1508,6 +1513,39 @@
_errorReporter.reportErrorForNode(code, node);
}
+ /// Attempt to evaluate a constant that reads the length of a `String`.
+ ///
+ /// Return a valid [DartObjectImpl] if the given [targetResult] represents a
+ /// `String` and the [identifier] is `length`, an [InvalidConstant] if there's
+ /// an error, and `null` otherwise.
+ Constant? _evaluateStringLength(DartObjectImpl targetResult,
+ SimpleIdentifier identifier, AstNode errorNode) {
+ if (identifier.staticElement?.enclosingElement is ExtensionElement) {
+ _errorReporter.reportErrorForNode(
+ CompileTimeErrorCode.CONST_EVAL_EXTENSION_METHOD, errorNode);
+ return InvalidConstant(
+ errorNode, CompileTimeErrorCode.CONST_EVAL_EXTENSION_METHOD);
+ }
+
+ if (identifier.name == 'length') {
+ final targetType = targetResult.type;
+ if (!(targetType is InterfaceType && targetType.isDartCoreString)) {
+ _errorReporter.reportErrorForNode(
+ CompileTimeErrorCode.CONST_EVAL_PROPERTY_ACCESS,
+ errorNode,
+ [identifier.name, targetType]);
+ return InvalidConstant(
+ errorNode, CompileTimeErrorCode.CONST_EVAL_PROPERTY_ACCESS,
+ arguments: [identifier.name, targetType]);
+ }
+ return targetResult.stringLength(typeSystem);
+ }
+
+ // TODO(kallentu): Make a more specific error here if we aren't accessing
+ // the '.length' property.
+ return null;
+ }
+
/// Return a [Constant], evaluated by the [ConstantVisitor].
///
/// The [ConstantVisitor] shouldn't return any `null` values even though
@@ -1787,19 +1825,6 @@
return value;
}
- /// Return `true` if the given [targetResult] represents a string and the
- /// [identifier] is "length".
- bool _isStringLength(
- DartObjectImpl targetResult, SimpleIdentifier identifier) {
- final targetType = targetResult.type;
- if (!(targetType is InterfaceType &&
- targetType.element == _typeProvider.stringElement)) {
- return false;
- }
- return identifier.name == 'length' &&
- identifier.staticElement?.enclosingElement is! ExtensionElement;
- }
-
/// Returns the first not-potentially constant error found with [node] or
/// `null` if there are none.
InvalidConstant? _reportNotPotentialConstants(AstNode node) {
diff --git a/pkg/analyzer/lib/src/error/codes.g.dart b/pkg/analyzer/lib/src/error/codes.g.dart
index c68c8bc..1b33782d 100644
--- a/pkg/analyzer/lib/src/error/codes.g.dart
+++ b/pkg/analyzer/lib/src/error/codes.g.dart
@@ -843,6 +843,16 @@
"Methods can't be invoked in constant expressions.",
);
+ /// Parameters:
+ /// 0: the name of the property being accessed
+ /// 1: the type with the property being accessed
+ static const CompileTimeErrorCode CONST_EVAL_PROPERTY_ACCESS =
+ CompileTimeErrorCode(
+ 'CONST_EVAL_PROPERTY_ACCESS',
+ "The property '{0}' can't be accessed on the type '{1}' in a constant "
+ "expression.",
+ );
+
/// 16.12.2 Const: It is a compile-time error if evaluation of a constant
/// object results in an uncaught exception being thrown.
static const CompileTimeErrorCode CONST_EVAL_THROWS_EXCEPTION =
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 dcd89b2..c5d6755 100644
--- a/pkg/analyzer/lib/src/error/error_code_values.g.dart
+++ b/pkg/analyzer/lib/src/error/error_code_values.g.dart
@@ -115,6 +115,7 @@
CompileTimeErrorCode.CONST_EVAL_EXTENSION_METHOD,
CompileTimeErrorCode.CONST_EVAL_FOR_ELEMENT,
CompileTimeErrorCode.CONST_EVAL_METHOD_INVOCATION,
+ CompileTimeErrorCode.CONST_EVAL_PROPERTY_ACCESS,
CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION,
CompileTimeErrorCode.CONST_EVAL_THROWS_IDBZE,
CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL,
diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml
index aa39512..116e9db 100644
--- a/pkg/analyzer/messages.yaml
+++ b/pkg/analyzer/messages.yaml
@@ -2587,6 +2587,12 @@
CONST_EVAL_FOR_ELEMENT:
problemMessage: "Constant expressions don't support 'for' elements."
correctionMessage: "Try replacing the 'for' element with a spread, or removing 'const'."
+ CONST_EVAL_PROPERTY_ACCESS:
+ problemMessage: "The property '{0}' can't be accessed on the type '{1}' in a constant expression."
+ comment: |-
+ Parameters:
+ 0: the name of the property being accessed
+ 1: the type with the property being accessed
CONST_EVAL_METHOD_INVOCATION:
problemMessage: "Methods can't be invoked in constant expressions."
CONST_EVAL_THROWS_EXCEPTION:
diff --git a/pkg/analyzer/test/src/dart/constant/evaluation_test.dart b/pkg/analyzer/test/src/dart/constant/evaluation_test.dart
index e7e3c36..bfa9ba2 100644
--- a/pkg/analyzer/test/src/dart/constant/evaluation_test.dart
+++ b/pkg/analyzer/test/src/dart/constant/evaluation_test.dart
@@ -1736,6 +1736,29 @@
''');
}
+ test_visitPropertyAccess_length_invalidTarget() async {
+ await assertErrorsInCode('''
+void main() {
+ const RequiresNonEmptyList([1]);
+}
+
+class RequiresNonEmptyList {
+ const RequiresNonEmptyList(List<int> numbers) : assert(numbers.length > 0);
+}
+''', [
+ error(
+ CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION,
+ 16,
+ 31,
+ contextMessages: [
+ ExpectedContextMessage(testFile.path, 138, 14,
+ text:
+ "The exception is 'The property 'length' can't be accessed on the type 'List<int>' in a constant expression.' and occurs here."),
+ ],
+ ),
+ ]);
+ }
+
test_visitRecordLiteral_objectField_generic() async {
await assertNoErrorsInCode(r'''
class A<T> {
@@ -2834,7 +2857,7 @@
''');
}
- test_visitPropertyAccess_fromExtension() async {
+ test_visitPropertyAccess_length_extension() async {
await assertErrorsInCode('''
extension ExtObject on Object {
int get length => 4;
@@ -2847,12 +2870,16 @@
const b = B('');
''', [
- error(CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION, 128, 5,
- contextMessages: [
- ExpectedContextMessage(testFile.path, 105, 8,
- text:
- "The exception is 'Invalid constant value.' and occurs here."),
- ]),
+ error(
+ CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION,
+ 128,
+ 5,
+ contextMessages: [
+ ExpectedContextMessage(testFile.path, 105, 8,
+ text:
+ "The exception is 'Extension methods can't be used in constant expressions.' 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 d4c9b60..0f75772 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
@@ -448,6 +448,29 @@
]);
}
+ test_property_length_invalidTarget() async {
+ await assertErrorsInCode('''
+void main() {
+ const RequiresNonEmptyList([1]);
+}
+
+class RequiresNonEmptyList {
+ const RequiresNonEmptyList(List<int> numbers) : assert(numbers.length > 0);
+}
+''', [
+ error(
+ CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION,
+ 16,
+ 31,
+ contextMessages: [
+ ExpectedContextMessage(testFile.path, 138, 14,
+ text:
+ "The exception is 'The property 'length' can't be accessed on the type 'List<int>' in a constant expression.' and occurs here."),
+ ],
+ ),
+ ]);
+ }
+
test_redirectingConstructor_paramTypeMismatch() async {
await assertErrorsInCode(r'''
class A {