[model] Update handling of ?.length in constants
This adds reporting of an error in CFE for ?.length in constant expressions and improves the message for the analyzer in the same case. The error in the analyzer was previously the invalid claim that
The property 'length' can't be accessed on the type 'Null' in a constant expression.
Closes #60509
Change-Id: Ibbe0fa1ace3bea9d83efea2ccf3ea9716a125d74
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/421841
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
index 876e857..dffd2fb 100644
--- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
@@ -2113,6 +2113,17 @@
);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeConstEvalNullAwareAccess = messageConstEvalNullAwareAccess;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageConstEvalNullAwareAccess = const MessageCode(
+ "ConstEvalNullAwareAccess",
+ analyzerCodes: <String>["CONST_EVAL_NULL_AWARE_ACCESS"],
+ problemMessage:
+ r"""Null-aware property access can't be used in a constant expression.""",
+);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeConstEvalNullValue = messageConstEvalNullValue;
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
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 9e2f2c4..fd78252 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
@@ -426,6 +426,8 @@
status: needsEvaluation
CompileTimeErrorCode.CONST_EVAL_METHOD_INVOCATION:
status: needsEvaluation
+CompileTimeErrorCode.CONST_EVAL_NULL_AWARE_ACCESS:
+ status: needsEvaluation
CompileTimeErrorCode.CONST_EVAL_PRIMITIVE_EQUALITY:
status: needsEvaluation
CompileTimeErrorCode.CONST_EVAL_PROPERTY_ACCESS:
diff --git a/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart b/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart
index 03781bb..6e32038 100644
--- a/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart
+++ b/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart
@@ -707,6 +707,10 @@
) ||
identical(
diagnosticCode,
+ CompileTimeErrorCode.CONST_EVAL_NULL_AWARE_ACCESS,
+ ) ||
+ identical(
+ diagnosticCode,
CompileTimeErrorCode.CONST_EVAL_PRIMITIVE_EQUALITY,
) ||
identical(
diff --git a/pkg/analyzer/lib/src/dart/constant/evaluation.dart b/pkg/analyzer/lib/src/dart/constant/evaluation.dart
index 8c68516..8bd49ff 100644
--- a/pkg/analyzer/lib/src/dart/constant/evaluation.dart
+++ b/pkg/analyzer/lib/src/dart/constant/evaluation.dart
@@ -1289,6 +1289,13 @@
return prefixResult;
}
+ if (node.isNullAware) {
+ return InvalidConstant.forEntity(
+ entity: node,
+ diagnosticCode: CompileTimeErrorCode.CONST_EVAL_NULL_AWARE_ACCESS,
+ );
+ }
+
var propertyAccessResult = _evaluatePropertyAccess(
prefixResult,
node.propertyName,
diff --git a/pkg/analyzer/lib/src/error/codes.g.dart b/pkg/analyzer/lib/src/error/codes.g.dart
index 7f14f82..8312151 100644
--- a/pkg/analyzer/lib/src/error/codes.g.dart
+++ b/pkg/analyzer/lib/src/error/codes.g.dart
@@ -1025,6 +1025,12 @@
"Methods can't be invoked in constant expressions.",
);
+ static const CompileTimeErrorCode CONST_EVAL_NULL_AWARE_ACCESS =
+ CompileTimeErrorCode(
+ 'CONST_EVAL_NULL_AWARE_ACCESS',
+ "Null-aware property access can't be used in a constant expression.",
+ );
+
/// See https://spec.dart.dev/DartLangSpecDraft.pdf#constants, "Constants",
/// for text about "An expression of the form e1 == e2".
static const CompileTimeErrorCode CONST_EVAL_PRIMITIVE_EQUALITY =
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 8f3a5a1..c49effd 100644
--- a/pkg/analyzer/lib/src/error/error_code_values.g.dart
+++ b/pkg/analyzer/lib/src/error/error_code_values.g.dart
@@ -136,6 +136,7 @@
CompileTimeErrorCode.CONST_EVAL_EXTENSION_TYPE_METHOD,
CompileTimeErrorCode.CONST_EVAL_FOR_ELEMENT,
CompileTimeErrorCode.CONST_EVAL_METHOD_INVOCATION,
+ CompileTimeErrorCode.CONST_EVAL_NULL_AWARE_ACCESS,
CompileTimeErrorCode.CONST_EVAL_PRIMITIVE_EQUALITY,
CompileTimeErrorCode.CONST_EVAL_PROPERTY_ACCESS,
CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION,
diff --git a/pkg/analyzer/lib/src/lint/constants.dart b/pkg/analyzer/lib/src/lint/constants.dart
index e6cfa3e..85961b1 100644
--- a/pkg/analyzer/lib/src/lint/constants.dart
+++ b/pkg/analyzer/lib/src/lint/constants.dart
@@ -49,6 +49,7 @@
case CompileTimeErrorCode.CONST_EVAL_EXTENSION_METHOD:
case CompileTimeErrorCode.CONST_EVAL_EXTENSION_TYPE_METHOD:
case CompileTimeErrorCode.CONST_EVAL_METHOD_INVOCATION:
+ case CompileTimeErrorCode.CONST_EVAL_NULL_AWARE_ACCESS:
case CompileTimeErrorCode.CONST_EVAL_PROPERTY_ACCESS:
case CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL:
case CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_INT:
diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml
index d59d233..6611817 100644
--- a/pkg/analyzer/messages.yaml
+++ b/pkg/analyzer/messages.yaml
@@ -2824,6 +2824,9 @@
problemMessage: "Constant expressions don't support 'for' elements."
correctionMessage: "Try replacing the 'for' element with a spread, or removing 'const'."
hasPublishedDocs: false
+ CONST_EVAL_NULL_AWARE_ACCESS:
+ problemMessage: "Null-aware property access can't be used in a constant expression."
+ hasPublishedDocs: false
CONST_EVAL_PROPERTY_ACCESS:
problemMessage: "The property '{0}' can't be accessed on the type '{1}' in a constant expression."
hasPublishedDocs: false
diff --git a/pkg/analyzer/test/src/diagnostics/const_eval_property_access_test.dart b/pkg/analyzer/test/src/diagnostics/const_eval_property_access_test.dart
index f8d9669..0cb44d9 100644
--- a/pkg/analyzer/test/src/diagnostics/const_eval_property_access_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/const_eval_property_access_test.dart
@@ -90,4 +90,13 @@
[error(CompileTimeErrorCode.CONST_EVAL_PROPERTY_ACCESS, 59, 11)],
);
}
+
+ test_null_aware_property_access() async {
+ await assertErrorsInCode(
+ r'''
+const String? s = null;
+const int? c = s?.length;''',
+ [error(CompileTimeErrorCode.CONST_EVAL_NULL_AWARE_ACCESS, 39, 9)],
+ );
+ }
}
diff --git a/pkg/front_end/lib/src/kernel/body_builder.dart b/pkg/front_end/lib/src/kernel/body_builder.dart
index 28985dd..84be4a72 100644
--- a/pkg/front_end/lib/src/kernel/body_builder.dart
+++ b/pkg/front_end/lib/src/kernel/body_builder.dart
@@ -2872,6 +2872,10 @@
]));
Object? send = pop();
if (send is Selector) {
+ if (send is PropertySelector && constantContext != ConstantContext.none) {
+ addProblem(
+ cfe.messageConstEvalNullAwareAccess, token.charOffset, noLength);
+ }
push(send.withReceiver(pop(), token.charOffset, isNullAware: true));
} else {
pop();
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index f3f0bb8..7b7d22d 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -7468,3 +7468,10 @@
void main() {
C c = .foo();
}
+
+ConstEvalNullAwareAccess:
+ problemMessage: "Null-aware property access can't be used in a constant expression."
+ analyzerCode: CONST_EVAL_NULL_AWARE_ACCESS
+ script: |
+ const String? s = null;
+ const c = s?.length;
\ No newline at end of file
diff --git a/pkg/front_end/testcases/general/constants/cascade.dart b/pkg/front_end/testcases/general/constants/cascade.dart
new file mode 100644
index 0000000..b88ac31
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/cascade.dart
@@ -0,0 +1,6 @@
+// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+const String s = '';
+const l = s..length;
diff --git a/pkg/front_end/testcases/general/constants/cascade.dart.strong.expect b/pkg/front_end/testcases/general/constants/cascade.dart.strong.expect
new file mode 100644
index 0000000..9f11ce0
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/cascade.dart.strong.expect
@@ -0,0 +1,17 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/constants/cascade.dart:6:11: Error: Not a constant expression.
+// const l = s..length;
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+static const field core::String s = #C1;
+static const field core::String l = invalid-expression "Not a constant expression.";
+
+constants {
+ #C1 = ""
+}
diff --git a/pkg/front_end/testcases/general/constants/cascade.dart.strong.modular.expect b/pkg/front_end/testcases/general/constants/cascade.dart.strong.modular.expect
new file mode 100644
index 0000000..9f11ce0
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/cascade.dart.strong.modular.expect
@@ -0,0 +1,17 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/constants/cascade.dart:6:11: Error: Not a constant expression.
+// const l = s..length;
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+static const field core::String s = #C1;
+static const field core::String l = invalid-expression "Not a constant expression.";
+
+constants {
+ #C1 = ""
+}
diff --git a/pkg/front_end/testcases/general/constants/cascade.dart.strong.outline.expect b/pkg/front_end/testcases/general/constants/cascade.dart.strong.outline.expect
new file mode 100644
index 0000000..5179d02
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/cascade.dart.strong.outline.expect
@@ -0,0 +1,15 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+static const field core::String s = "";
+static const field core::String l = let final core::String #t1 = self::s in block {
+ #t1.{core::String::length}{core::int};
+} =>#t1;
+
+
+Extra constant evaluation status:
+Evaluated: StaticGet @ org-dartlang-testcase:///cascade.dart:6:11 -> StringConstant("")
+Evaluated: InstanceGet @ org-dartlang-testcase:///cascade.dart:6:14 -> IntConstant(0)
+Evaluated: VariableGet @ org-dartlang-testcase:///cascade.dart:6:11 -> StringConstant("")
+Extra constant evaluation: evaluated: 5, effectively constant: 3
diff --git a/pkg/front_end/testcases/general/constants/cascade.dart.strong.transformed.expect b/pkg/front_end/testcases/general/constants/cascade.dart.strong.transformed.expect
new file mode 100644
index 0000000..9f11ce0
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/cascade.dart.strong.transformed.expect
@@ -0,0 +1,17 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/constants/cascade.dart:6:11: Error: Not a constant expression.
+// const l = s..length;
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+static const field core::String s = #C1;
+static const field core::String l = invalid-expression "Not a constant expression.";
+
+constants {
+ #C1 = ""
+}
diff --git a/pkg/front_end/testcases/general/constants/cascade.dart.textual_outline.expect b/pkg/front_end/testcases/general/constants/cascade.dart.textual_outline.expect
new file mode 100644
index 0000000..9be0679
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/cascade.dart.textual_outline.expect
@@ -0,0 +1,3 @@
+const String s = '';
+
+const l = s..length;
diff --git a/pkg/front_end/testcases/general/constants/cascade.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/constants/cascade.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..9be0679
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/cascade.dart.textual_outline_modelled.expect
@@ -0,0 +1,3 @@
+const String s = '';
+
+const l = s..length;
diff --git a/pkg/front_end/testcases/general/constants/null_aware_string_length.dart b/pkg/front_end/testcases/general/constants/null_aware_string_length.dart
new file mode 100644
index 0000000..246306c
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/null_aware_string_length.dart
@@ -0,0 +1,6 @@
+// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+const String? s = null;
+const int? c = s?.length; // Error in the analyzer, no error in CFE
diff --git a/pkg/front_end/testcases/general/constants/null_aware_string_length.dart.strong.expect b/pkg/front_end/testcases/general/constants/null_aware_string_length.dart.strong.expect
new file mode 100644
index 0000000..ff44c31
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/null_aware_string_length.dart.strong.expect
@@ -0,0 +1,17 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/constants/null_aware_string_length.dart:6:17: Error: Null-aware property access can't be used in a constant expression.
+// const int? c = s?.length; // Error in the analyzer, no error in CFE
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+static const field core::String? s = #C1;
+static const field core::int? c = #C1;
+
+constants {
+ #C1 = null
+}
diff --git a/pkg/front_end/testcases/general/constants/null_aware_string_length.dart.strong.modular.expect b/pkg/front_end/testcases/general/constants/null_aware_string_length.dart.strong.modular.expect
new file mode 100644
index 0000000..ff44c31
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/null_aware_string_length.dart.strong.modular.expect
@@ -0,0 +1,17 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/constants/null_aware_string_length.dart:6:17: Error: Null-aware property access can't be used in a constant expression.
+// const int? c = s?.length; // Error in the analyzer, no error in CFE
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+static const field core::String? s = #C1;
+static const field core::int? c = #C1;
+
+constants {
+ #C1 = null
+}
diff --git a/pkg/front_end/testcases/general/constants/null_aware_string_length.dart.strong.outline.expect b/pkg/front_end/testcases/general/constants/null_aware_string_length.dart.strong.outline.expect
new file mode 100644
index 0000000..29416c3
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/null_aware_string_length.dart.strong.outline.expect
@@ -0,0 +1,18 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/constants/null_aware_string_length.dart:6:17: Error: Null-aware property access can't be used in a constant expression.
+// const int? c = s?.length; // Error in the analyzer, no error in CFE
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+static const field core::String? s = null;
+static const field core::int? c = let final core::String? #t1 = self::s in #t1 == null ?{core::int?} null : #t1{core::String}.{core::String::length}{core::int};
+
+
+Extra constant evaluation status:
+Evaluated: Let @ org-dartlang-testcase:///null_aware_string_length.dart:6:16 -> NullConstant(null)
+Extra constant evaluation: evaluated: 1, effectively constant: 1
diff --git a/pkg/front_end/testcases/general/constants/null_aware_string_length.dart.strong.transformed.expect b/pkg/front_end/testcases/general/constants/null_aware_string_length.dart.strong.transformed.expect
new file mode 100644
index 0000000..ff44c31
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/null_aware_string_length.dart.strong.transformed.expect
@@ -0,0 +1,17 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/constants/null_aware_string_length.dart:6:17: Error: Null-aware property access can't be used in a constant expression.
+// const int? c = s?.length; // Error in the analyzer, no error in CFE
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+static const field core::String? s = #C1;
+static const field core::int? c = #C1;
+
+constants {
+ #C1 = null
+}
diff --git a/pkg/front_end/testcases/general/constants/null_aware_string_length.dart.textual_outline.expect b/pkg/front_end/testcases/general/constants/null_aware_string_length.dart.textual_outline.expect
new file mode 100644
index 0000000..96c9fa6
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/null_aware_string_length.dart.textual_outline.expect
@@ -0,0 +1,3 @@
+const String? s = null;
+
+const int? c = s?.length;
diff --git a/pkg/front_end/testcases/general/constants/null_aware_string_length.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/constants/null_aware_string_length.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..96c9fa6
--- /dev/null
+++ b/pkg/front_end/testcases/general/constants/null_aware_string_length.dart.textual_outline_modelled.expect
@@ -0,0 +1,3 @@
+const String? s = null;
+
+const int? c = s?.length;