[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 {