[CFE] Specific error message when using unavailable variables in expression evaluation
So instead of either being told that a variable doesn't exist, or
using a field or something instead of a local, you'll now get a message
saying "<variable> is unavailable in this expression evaluation".
Bug: https://github.com/dart-lang/sdk/issues/53996
Bug: https://github.com/dart-lang/sdk/issues/53087
Bug: https://github.com/dart-lang/sdk/issues/45913 (sort of?)
Bug: https://github.com/dart-lang/sdk/issues/53688
Change-Id: I6726209584e414ddc40f3e7ff6bffa9e7283e9d0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/445701
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Ben Konyi <bkonyi@google.com>
Commit-Queue: Jens Johansen <jensj@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 bb3b0ab..1c77f02 100644
--- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
@@ -3733,6 +3733,31 @@
);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Template<Message Function(String name)>
+codeExpressionEvaluationKnownVariableUnavailable = const Template<
+ Message Function(String name)
+>(
+ "ExpressionEvaluationKnownVariableUnavailable",
+ problemMessageTemplate:
+ r"""The variable '#name' is unavailable in this expression evaluation.""",
+ withArguments: _withArgumentsExpressionEvaluationKnownVariableUnavailable,
+);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+Message _withArgumentsExpressionEvaluationKnownVariableUnavailable(
+ String name,
+) {
+ if (name.isEmpty) throw 'No name provided';
+ name = demangleMixinApplicationName(name);
+ return new Message(
+ codeExpressionEvaluationKnownVariableUnavailable,
+ problemMessage:
+ """The variable '${name}' is unavailable in this expression evaluation.""",
+ arguments: {'name': name},
+ );
+}
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const MessageCode codeExpressionNotMetadata = const MessageCode(
"ExpressionNotMetadata",
problemMessage:
diff --git a/pkg/front_end/lib/src/base/incremental_compiler.dart b/pkg/front_end/lib/src/base/incremental_compiler.dart
index 77ecce4..9e08bd4 100644
--- a/pkg/front_end/lib/src/base/incremental_compiler.dart
+++ b/pkg/front_end/lib/src/base/incremental_compiler.dart
@@ -9,6 +9,7 @@
import 'package:_fe_analyzer_shared/src/scanner/abstract_scanner.dart'
show ScannerConfiguration;
import 'package:front_end/src/base/name_space.dart';
+import 'package:front_end/src/type_inference/inference_results.dart';
import 'package:kernel/binary/ast_from_binary.dart'
show
BinaryBuilderWithMetadata,
@@ -50,7 +51,9 @@
TypeParameter,
VariableDeclaration,
VisitorDefault,
- VisitorVoidMixin;
+ VisitorVoidMixin,
+ VariableGet,
+ VariableSet;
import 'package:kernel/kernel.dart' as kernel show Combinator;
import 'package:kernel/reference_from_index.dart';
import 'package:kernel/target/changed_structure_notifier.dart'
@@ -87,6 +90,9 @@
import '../source/source_library_builder.dart'
show ImplicitLanguageVersion, SourceLibraryBuilder;
import '../source/source_loader.dart';
+import '../type_inference/inference_helper.dart' show InferenceHelper;
+import '../type_inference/inference_visitor.dart'
+ show ExpressionEvaluationHelper;
import '../util/error_reporter_file_copier.dart' show saveAsGzip;
import '../util/experiment_environment_getter.dart'
show enableIncrementalCompilerBenchmarking, getExperimentEnvironment;
@@ -1693,6 +1699,21 @@
hasDeclaredInitializer: true,
initializer: def.value.initializer)
..fileOffset = def.value.fileOffset);
+ } else if (def.value.isInitializingFormal ||
+ def.value.isSuperInitializingFormal) {
+ // An (super) initializing formal parameter of a constructor
+ // should not shadow the field it was used to initialize,
+ // so we'll ignore it.
+ } else {
+ // Non-const variable we should know about but wasn't told
+ // about. Maybe the variable was optimized out? Maybe it wasn't
+ // captured? Either way there's something shadowing any fields
+ // etc.
+ extraKnownVariables.add(new VariableDeclarationImpl(
+ def.key,
+ type: def.value.type,
+ isConst: false,
+ )..fileOffset = def.value.fileOffset);
}
} else if (existingType is DynamicType ||
_ExtensionTypeFinder.isOrContainsExtensionType(
@@ -1932,6 +1953,9 @@
new Name(syntheticProcedureName), ProcedureKind.Method, parameters,
isStatic: isStatic, fileUri: debugLibrary.fileUri);
+ ExpressionEvaluationHelper expressionEvaluationHelper =
+ new ExpressionEvaluationHelperImpl(extraKnownVariables);
+
Expression compiledExpression = await lastGoodKernelTarget.loader
.buildExpression(
debugLibrary,
@@ -1939,7 +1963,8 @@
(className != null && !isStatic) || extensionThis != null,
procedure,
extensionThis,
- extraKnownVariables);
+ extraKnownVariables,
+ expressionEvaluationHelper);
parameters.body = new ReturnStatement(compiledExpression)
..parent = parameters;
@@ -2161,6 +2186,55 @@
}
// Coverage-ignore(suite): Not run.
+class ExpressionEvaluationHelperImpl implements ExpressionEvaluationHelper {
+ final Set<VariableDeclarationImpl> knownButUnavailable = {};
+
+ ExpressionEvaluationHelperImpl(List<VariableDeclarationImpl> extraKnown) {
+ for (VariableDeclarationImpl variable in extraKnown) {
+ if (variable.isConst) {
+ // We allow const variables - these are inlined (we check
+ // `alwaysInlineConstants` in `compileExpression`).
+ continue;
+ }
+ knownButUnavailable.add(variable);
+ }
+ }
+
+ @override
+ ExpressionInferenceResult? visitVariableGet(
+ VariableGet node, DartType typeContext, InferenceHelper helper) {
+ if (knownButUnavailable.contains(node.variable)) {
+ return _returnKnownVariableUnavailable(node, node.variable, helper);
+ }
+ return null;
+ }
+
+ @override
+ ExpressionInferenceResult? visitVariableSet(
+ VariableSet node, DartType typeContext, InferenceHelper helper) {
+ if (knownButUnavailable.contains(node.variable)) {
+ return _returnKnownVariableUnavailable(node, node.variable, helper);
+ }
+ return null;
+ }
+
+ ExpressionInferenceResult _returnKnownVariableUnavailable(
+ Expression node, VariableDeclaration variable, InferenceHelper helper) {
+ return new ExpressionInferenceResult(
+ variable.type,
+ helper.wrapInProblem(
+ node,
+ codeExpressionEvaluationKnownVariableUnavailable
+ .withArguments(variable.name!),
+ node.fileOffset,
+ variable.name!.length,
+ errorHasBeenReported: false,
+ includeExpression: false,
+ ));
+ }
+}
+
+// Coverage-ignore(suite): Not run.
class _ExtensionTypeFinder extends VisitorDefault<void> with VisitorVoidMixin {
static bool isOrContainsExtensionType(DartType type) {
if (type is ExtensionType) return true;
diff --git a/pkg/front_end/lib/src/kernel/body_builder.dart b/pkg/front_end/lib/src/kernel/body_builder.dart
index 44aa7b2..3424847 100644
--- a/pkg/front_end/lib/src/kernel/body_builder.dart
+++ b/pkg/front_end/lib/src/kernel/body_builder.dart
@@ -112,6 +112,8 @@
import '../source/value_kinds.dart';
import '../type_inference/inference_results.dart'
show InitializerInferenceResult;
+import '../type_inference/inference_visitor.dart'
+ show ExpressionEvaluationHelper;
import '../type_inference/type_inferrer.dart'
show TypeInferrer, InferredFunctionBody;
import '../type_inference/type_schema.dart' show UnknownType;
@@ -1253,7 +1255,8 @@
_context.memberNameOffset,
_context.returnTypeContext,
asyncModifier,
- body);
+ body,
+ null);
body = inferredFunctionBody.body;
function.emittedValueType = inferredFunctionBody.emittedValueType;
assert(function.asyncMarker == AsyncMarker.Sync ||
@@ -1590,7 +1593,8 @@
Parser parser,
Token token,
FunctionNode parameters,
- List<VariableDeclarationImpl> extraKnownVariables) {
+ List<VariableDeclarationImpl> extraKnownVariables,
+ ExpressionEvaluationHelper expressionEvaluationHelper) {
int fileOffset = offsetForToken(token);
List<NominalParameterBuilder>? typeParameterBuilders;
for (TypeParameter typeParameter in parameters.typeParameters) {
@@ -1678,7 +1682,12 @@
}
InferredFunctionBody inferredFunctionBody = typeInferrer.inferFunctionBody(
- this, fileOffset, const DynamicType(), AsyncMarker.Sync, fakeReturn);
+ this,
+ fileOffset,
+ const DynamicType(),
+ AsyncMarker.Sync,
+ fakeReturn,
+ expressionEvaluationHelper);
assert(
fakeReturn == inferredFunctionBody.body,
"Previously implicit assumption about inferFunctionBody "
@@ -9085,13 +9094,17 @@
@override
Expression wrapInProblem(
Expression expression, Message message, int fileOffset, int length,
- {List<LocatedMessage>? context}) {
+ {List<LocatedMessage>? context,
+ bool? errorHasBeenReported,
+ bool includeExpression = true}) {
CfeSeverity severity = message.code.severity;
if (severity == CfeSeverity.error) {
return wrapInLocatedProblem(
expression, message.withLocation(uri, fileOffset, length),
context: context,
- errorHasBeenReported: expression is InvalidExpression);
+ errorHasBeenReported:
+ errorHasBeenReported ?? expression is InvalidExpression,
+ includeExpression: includeExpression);
} else {
// Coverage-ignore-block(suite): Not run.
if (expression is! InvalidExpression) {
@@ -9103,7 +9116,9 @@
@override
Expression wrapInLocatedProblem(Expression expression, LocatedMessage message,
- {List<LocatedMessage>? context, bool errorHasBeenReported = false}) {
+ {List<LocatedMessage>? context,
+ bool errorHasBeenReported = false,
+ bool includeExpression = true}) {
// TODO(askesc): Produce explicit error expression wrapping the original.
// See [issue 29717](https://github.com/dart-lang/sdk/issues/29717)
int offset = expression.fileOffset;
@@ -9113,7 +9128,7 @@
return buildProblem(
message.messageObject, message.charOffset, message.length,
context: context,
- expression: expression,
+ expression: includeExpression ? expression : null,
errorHasBeenReported: errorHasBeenReported);
}
diff --git a/pkg/front_end/lib/src/kernel/constant_evaluator.dart b/pkg/front_end/lib/src/kernel/constant_evaluator.dart
index 5f44a8c..69090ea 100644
--- a/pkg/front_end/lib/src/kernel/constant_evaluator.dart
+++ b/pkg/front_end/lib/src/kernel/constant_evaluator.dart
@@ -407,10 +407,7 @@
makeConstantExpression(constant, initializer)..parent = node;
// If this constant is inlined, remove it.
- if (!keepLocals &&
- // Coverage-ignore(suite): Not run.
- shouldInline(initializer)) {
- // Coverage-ignore-block(suite): Not run.
+ if (!keepLocals && shouldInline(initializer)) {
if (constant is! UnevaluatedConstant) {
// If the constant is unevaluated we need to keep the expression,
// so that, in the case the constant contains error but the local
@@ -593,7 +590,6 @@
}
}
if (storeIndex < node.statements.length) {
- // Coverage-ignore-block(suite): Not run.
node.statements.length = storeIndex;
}
return node;
diff --git a/pkg/front_end/lib/src/kernel/expression_generator_helper.dart b/pkg/front_end/lib/src/kernel/expression_generator_helper.dart
index d3d928a..4c920a3 100644
--- a/pkg/front_end/lib/src/kernel/expression_generator_helper.dart
+++ b/pkg/front_end/lib/src/kernel/expression_generator_helper.dart
@@ -149,7 +149,7 @@
Message message, int charOffset, int length);
Expression wrapInLocatedProblem(Expression expression, LocatedMessage message,
- {List<LocatedMessage>? context});
+ {List<LocatedMessage>? context, bool includeExpression = true});
Expression evaluateArgumentsBefore(
Arguments arguments, Expression expression);
diff --git a/pkg/front_end/lib/src/source/source_loader.dart b/pkg/front_end/lib/src/source/source_loader.dart
index b050d88..056b533 100644
--- a/pkg/front_end/lib/src/source/source_loader.dart
+++ b/pkg/front_end/lib/src/source/source_loader.dart
@@ -70,6 +70,8 @@
show DelayedDefaultValueCloner, TypeDependency;
import '../kernel/kernel_target.dart' show KernelTarget;
import '../kernel/type_builder_computer.dart' show TypeBuilderComputer;
+import '../type_inference/inference_visitor.dart'
+ show ExpressionEvaluationHelper;
import '../type_inference/type_inference_engine.dart';
import '../type_inference/type_inferrer.dart';
import '../util/reference_map.dart';
@@ -1249,7 +1251,8 @@
bool isClassInstanceMember,
Procedure procedure,
VariableDeclaration? extensionThis,
- List<VariableDeclarationImpl> extraKnownVariables) async {
+ List<VariableDeclarationImpl> extraKnownVariables,
+ ExpressionEvaluationHelper expressionEvaluationHelper) async {
// TODO(johnniwinther): Support expression compilation in a specific
// compilation unit.
LookupScope memberScope =
@@ -1308,14 +1311,16 @@
}
return listener.parseSingleExpression(
- new Parser(listener,
- useImplicitCreationExpression: useImplicitCreationExpressionInCfe,
- allowPatterns: libraryBuilder.libraryFeatures.patterns.isEnabled,
- enableFeatureEnhancedParts:
- libraryBuilder.libraryFeatures.enhancedParts.isEnabled),
- token,
- procedure.function,
- extraKnownVariables);
+ new Parser(listener,
+ useImplicitCreationExpression: useImplicitCreationExpressionInCfe,
+ allowPatterns: libraryBuilder.libraryFeatures.patterns.isEnabled,
+ enableFeatureEnhancedParts:
+ libraryBuilder.libraryFeatures.enhancedParts.isEnabled),
+ token,
+ procedure.function,
+ extraKnownVariables,
+ expressionEvaluationHelper,
+ );
}
DietListener createDietListener(SourceLibraryBuilder library,
diff --git a/pkg/front_end/lib/src/type_inference/inference_helper.dart b/pkg/front_end/lib/src/type_inference/inference_helper.dart
index 68bf331..5745e45 100644
--- a/pkg/front_end/lib/src/type_inference/inference_helper.dart
+++ b/pkg/front_end/lib/src/type_inference/inference_helper.dart
@@ -24,7 +24,9 @@
Expression wrapInProblem(
Expression expression, Message message, int fileOffset, int length,
- {List<LocatedMessage>? context});
+ {List<LocatedMessage>? context,
+ bool? errorHasBeenReported,
+ bool includeExpression = true});
String superConstructorNameForDiagnostics(String name);
diff --git a/pkg/front_end/lib/src/type_inference/inference_visitor.dart b/pkg/front_end/lib/src/type_inference/inference_visitor.dart
index 643c22b..334a2ff 100644
--- a/pkg/front_end/lib/src/type_inference/inference_visitor.dart
+++ b/pkg/front_end/lib/src/type_inference/inference_visitor.dart
@@ -174,8 +174,17 @@
// variable was declared outside the try statement or local function.
bool _inTryOrLocalFunction = false;
- InferenceVisitorImpl(TypeInferrerImpl inferrer, InferenceHelper helper,
- this._constructorBuilder, this.operations, this.typeAnalyzerOptions)
+ /// Helper used to issue correct error messages and avoid access to
+ /// unavailable variables upon expression evaluation.
+ final ExpressionEvaluationHelper? expressionEvaluationHelper;
+
+ InferenceVisitorImpl(
+ TypeInferrerImpl inferrer,
+ InferenceHelper helper,
+ this._constructorBuilder,
+ this.operations,
+ this.typeAnalyzerOptions,
+ this.expressionEvaluationHelper)
: super(inferrer, helper);
@override
@@ -9252,6 +9261,14 @@
@override
ExpressionInferenceResult visitVariableSet(
VariableSet node, DartType typeContext) {
+ if (expressionEvaluationHelper != null) {
+ // Coverage-ignore-block(suite): Not run.
+ ExpressionInferenceResult? result = expressionEvaluationHelper
+ ?.visitVariableSet(node, typeContext, helper);
+ if (result != null) {
+ return result;
+ }
+ }
VariableDeclarationImpl variable = node.variable as VariableDeclarationImpl;
bool isDefinitelyAssigned = flowAnalysis.isAssigned(variable);
bool isDefinitelyUnassigned = flowAnalysis.isUnassigned(variable);
@@ -9551,6 +9568,14 @@
@override
ExpressionInferenceResult visitVariableGet(
VariableGet node, DartType typeContext) {
+ if (expressionEvaluationHelper != null) {
+ // Coverage-ignore-block(suite): Not run.
+ ExpressionInferenceResult? result = expressionEvaluationHelper
+ ?.visitVariableGet(node, typeContext, helper);
+ if (result != null) {
+ return result;
+ }
+ }
if (node is! VariableGetImpl) {
// Coverage-ignore-block(suite): Not run.
// This node is created as part of a lowering and doesn't need inference.
@@ -12375,3 +12400,11 @@
inferredSpreadTypes: inferredSpreadTypes,
inferredConditionTypes: inferredConditionTypes);
}
+
+abstract class ExpressionEvaluationHelper {
+ ExpressionInferenceResult? visitVariableGet(
+ VariableGet node, DartType typeContext, InferenceHelper helper);
+
+ ExpressionInferenceResult? visitVariableSet(
+ VariableSet node, DartType typeContext, InferenceHelper helper);
+}
diff --git a/pkg/front_end/lib/src/type_inference/type_inferrer.dart b/pkg/front_end/lib/src/type_inference/type_inferrer.dart
index 10416c2..a987e10 100644
--- a/pkg/front_end/lib/src/type_inference/type_inferrer.dart
+++ b/pkg/front_end/lib/src/type_inference/type_inferrer.dart
@@ -51,8 +51,13 @@
InferenceHelper helper, DartType declaredType, Expression initializer);
/// Performs type inference on the given function body.
- InferredFunctionBody inferFunctionBody(InferenceHelper helper, int fileOffset,
- DartType returnType, AsyncMarker asyncMarker, Statement body);
+ InferredFunctionBody inferFunctionBody(
+ InferenceHelper helper,
+ int fileOffset,
+ DartType returnType,
+ AsyncMarker asyncMarker,
+ Statement body,
+ ExpressionEvaluationHelper? expressionEvaluationHelper);
/// Performs type inference on the given constructor initializer.
InitializerInferenceResult inferInitializer(InferenceHelper helper,
@@ -155,11 +160,12 @@
libraryBuilder.libraryFeatures.soundFlowAnalysis.isEnabled);
InferenceVisitorBase _createInferenceVisitor(InferenceHelper helper,
- [SourceConstructorBuilder? constructorBuilder]) {
+ {SourceConstructorBuilder? constructorBuilder,
+ ExpressionEvaluationHelper? expressionEvaluationHelper}) {
// For full (non-top level) inference, we need access to the
// InferenceHelper so that we can perform error reporting.
- return new InferenceVisitorImpl(
- this, helper, constructorBuilder, operations, typeAnalyzerOptions);
+ return new InferenceVisitorImpl(this, helper, constructorBuilder,
+ operations, typeAnalyzerOptions, expressionEvaluationHelper);
}
@override
@@ -188,9 +194,16 @@
}
@override
- InferredFunctionBody inferFunctionBody(InferenceHelper helper, int fileOffset,
- DartType returnType, AsyncMarker asyncMarker, Statement body) {
- InferenceVisitorBase visitor = _createInferenceVisitor(helper);
+ InferredFunctionBody inferFunctionBody(
+ InferenceHelper helper,
+ int fileOffset,
+ DartType returnType,
+ AsyncMarker asyncMarker,
+ Statement body,
+ ExpressionEvaluationHelper? expressionEvaluationHelper,
+ ) {
+ InferenceVisitorBase visitor = _createInferenceVisitor(helper,
+ expressionEvaluationHelper: expressionEvaluationHelper);
ClosureContext closureContext =
new ClosureContext(visitor, asyncMarker, returnType, false);
StatementInferenceResult result =
@@ -268,7 +281,7 @@
// so that the type hierarchy will be simpler (which may speed up "is"
// checks).
InferenceVisitorBase visitor =
- _createInferenceVisitor(helper, constructorBuilder);
+ _createInferenceVisitor(helper, constructorBuilder: constructorBuilder);
InitializerInferenceResult result = visitor.inferInitializer(initializer);
visitor.checkCleanState();
return result;
@@ -361,11 +374,17 @@
}
@override
- InferredFunctionBody inferFunctionBody(InferenceHelper helper, int fileOffset,
- DartType returnType, AsyncMarker asyncMarker, Statement body) {
+ InferredFunctionBody inferFunctionBody(
+ InferenceHelper helper,
+ int fileOffset,
+ DartType returnType,
+ AsyncMarker asyncMarker,
+ Statement body,
+ ExpressionEvaluationHelper? expressionEvaluationHelper,
+ ) {
benchmarker.beginSubdivide(BenchmarkSubdivides.inferFunctionBody);
- InferredFunctionBody result = impl.inferFunctionBody(
- helper, fileOffset, returnType, asyncMarker, body);
+ InferredFunctionBody result = impl.inferFunctionBody(helper, fileOffset,
+ returnType, asyncMarker, body, expressionEvaluationHelper);
benchmarker.endSubdivide();
return result;
}
diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status
index 0a06a36..ba4f8f6 100644
--- a/pkg/front_end/messages.status
+++ b/pkg/front_end/messages.status
@@ -41,6 +41,7 @@
ExperimentExpiredEnabled/example: missingExample # Uncovered.
ExperimentNotEnabled/example: missingExample # issued via the parser, but overridden by StackListenerImpl --- so likely not possible via CFE outside of special tests?
ExplicitExtensionAsLvalue/example: missingExample # Uncovered.
+ExpressionEvaluationKnownVariableUnavailable/example: missingExample # Expression compilation.
ExtensionAugmentationHasOnClause/part_wrapped_script: hasOnlyUnrelatedMessages # Doesn't seem to be any (direct?) way to get this for now
ExtensionAugmentationHasOnClause/script: hasOnlyUnrelatedMessages # Doesn't seem to be any (direct?) way to get this for now
ExtensionTypeShouldBeListedAsCallableInDynamicInterface/example: missingExample # Can't do dynamic modules stuff in messages suite.
@@ -249,6 +250,7 @@
ExplicitExtensionAsExpression/analyzerCode: missingAnalyzerCode
ExplicitExtensionAsLvalue/analyzerCode: missingAnalyzerCode
ExplicitExtensionTypeArgumentMismatch/analyzerCode: missingAnalyzerCode
+ExpressionEvaluationKnownVariableUnavailable/analyzerCode: missingAnalyzerCode
ExpressionNotMetadata/analyzerCode: missingAnalyzerCode
ExtendsNever/analyzerCode: missingAnalyzerCode # Feature not yet in analyzer.
ExtensionMemberConflictsWithObjectMember/analyzerCode: missingAnalyzerCode
@@ -515,4 +517,4 @@
WeakReferenceReturnTypeNotNullable/analyzerCode: missingAnalyzerCode
WeakReferenceTargetHasParameters/analyzerCode: missingAnalyzerCode
WeakReferenceTargetNotStaticTearoff/analyzerCode: missingAnalyzerCode
-WebLiteralCannotBeRepresentedExactly/analyzerCode: missingAnalyzerCode
\ No newline at end of file
+WebLiteralCannotBeRepresentedExactly/analyzerCode: missingAnalyzerCode
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index 48866ab..3eca544 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -8346,3 +8346,6 @@
void main() {
C c = .foo();
}
+
+ExpressionEvaluationKnownVariableUnavailable:
+ problemMessage: "The variable '#name' is unavailable in this expression evaluation."
diff --git a/pkg/front_end/testcases/expression/issue_53996_01.expression.yaml b/pkg/front_end/testcases/expression/issue_53996_01.expression.yaml
new file mode 100644
index 0000000..dbc5a39
--- /dev/null
+++ b/pkg/front_end/testcases/expression/issue_53996_01.expression.yaml
@@ -0,0 +1,59 @@
+# 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.
+
+# https://github.com/dart-lang/sdk/issues/53996
+
+# Definition, offset, method etc extracted by starting the VM with
+# `-DDFE_VERBOSE=true`, e.g.
+# ```
+# out/ReleaseX64/dart -DDFE_VERBOSE=true --enable-vm-service \
+# --disable-service-auth-codes --pause_isolates_on_start inputFile.dart
+# ```
+# and then issuing the expression compilation.
+
+sources: |
+ import "dart:developer";
+
+ bool debug = false;
+
+ main() {
+ for(int i = 0; i < 50000; i++) {
+ foo();
+ }
+ debug = true;
+ foo();
+ }
+
+ foo() {
+ List<int> data = [1, 2, 3];
+ for(int i in data) {
+ if (bar(i)) {
+ break;
+ }
+ }
+ }
+
+ bool bar(int i) {
+ if (i == 2) {
+ if (debug) {
+ debugger();
+ print("set a breakpoint here!");
+ print("woke up");
+ }
+ return true;
+ }
+ return false;
+ }
+
+definitions: []
+definition_types: []
+type_definitions: []
+type_bounds: []
+type_defaults: []
+method: "foo"
+static: true
+offset: 219
+scriptUri: main.dart
+expression: |
+ data.length
diff --git a/pkg/front_end/testcases/expression/issue_53996_01.expression.yaml.expect b/pkg/front_end/testcases/expression/issue_53996_01.expression.yaml.expect
new file mode 100644
index 0000000..5262cbb
--- /dev/null
+++ b/pkg/front_end/testcases/expression/issue_53996_01.expression.yaml.expect
@@ -0,0 +1,7 @@
+Errors: {
+ org-dartlang-debug:synthetic_debug_expression:1:1: Error: The variable 'data' is unavailable in this expression evaluation.
+ data.length
+ ^^^^
+}
+static method /* from org-dartlang-debug:synthetic_debug_expression */ debugExpr() → dynamic
+ return invalid-expression "org-dartlang-debug:synthetic_debug_expression:1:1: Error: The variable 'data' is unavailable in this expression evaluation.\ndata.length\n^^^^".{dart.core::List::length}{dart.core::int};
diff --git a/pkg/front_end/testcases/expression/issue_53996_02.expression.yaml b/pkg/front_end/testcases/expression/issue_53996_02.expression.yaml
new file mode 100644
index 0000000..b63f68d
--- /dev/null
+++ b/pkg/front_end/testcases/expression/issue_53996_02.expression.yaml
@@ -0,0 +1,62 @@
+# 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.
+
+# https://github.com/dart-lang/sdk/issues/53996
+
+# Definition, offset, method etc extracted by starting the VM with
+# `-DDFE_VERBOSE=true`, e.g.
+# ```
+# out/ReleaseX64/dart -DDFE_VERBOSE=true --enable-vm-service \
+# --disable-service-auth-codes --pause_isolates_on_start inputFile.dart
+# ```
+# and then issuing the expression compilation.
+
+sources: |
+ import "dart:developer";
+
+ bool debug = false;
+
+ String data = "hello from top level field";
+
+ main() {
+ for(int i = 0; i < 50000; i++) {
+ foo();
+ }
+ debug = true;
+ foo();
+ print(data);
+ }
+
+ foo() {
+ List<int> data = [1, 2, 3];
+ for(int i in data) {
+ if (bar(i)) {
+ break;
+ }
+ }
+ }
+
+ bool bar(int i) {
+ if (i == 2) {
+ if (debug) {
+ debugger();
+ print("set a breakpoint here!");
+ print("woke up");
+ }
+ return true;
+ }
+ return false;
+ }
+
+definitions: []
+definition_types: []
+type_definitions: []
+type_bounds: []
+type_defaults: []
+method: "foo"
+static: true
+offset: 279
+scriptUri: main.dart
+expression: |
+ data.length
diff --git a/pkg/front_end/testcases/expression/issue_53996_02.expression.yaml.expect b/pkg/front_end/testcases/expression/issue_53996_02.expression.yaml.expect
new file mode 100644
index 0000000..5262cbb
--- /dev/null
+++ b/pkg/front_end/testcases/expression/issue_53996_02.expression.yaml.expect
@@ -0,0 +1,7 @@
+Errors: {
+ org-dartlang-debug:synthetic_debug_expression:1:1: Error: The variable 'data' is unavailable in this expression evaluation.
+ data.length
+ ^^^^
+}
+static method /* from org-dartlang-debug:synthetic_debug_expression */ debugExpr() → dynamic
+ return invalid-expression "org-dartlang-debug:synthetic_debug_expression:1:1: Error: The variable 'data' is unavailable in this expression evaluation.\ndata.length\n^^^^".{dart.core::List::length}{dart.core::int};
diff --git a/pkg/front_end/testcases/expression/issue_53996_03.expression.yaml b/pkg/front_end/testcases/expression/issue_53996_03.expression.yaml
new file mode 100644
index 0000000..5fa22f5
--- /dev/null
+++ b/pkg/front_end/testcases/expression/issue_53996_03.expression.yaml
@@ -0,0 +1,59 @@
+# 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.
+
+# https://github.com/dart-lang/sdk/issues/53996
+
+# Definition, offset, method etc extracted by starting the VM with
+# `-DDFE_VERBOSE=true`, e.g.
+# ```
+# out/ReleaseX64/dart -DDFE_VERBOSE=true --enable-vm-service \
+# --disable-service-auth-codes --pause_isolates_on_start inputFile.dart
+# ```
+# and then issuing the expression compilation.
+
+sources: |
+ import "dart:developer";
+
+ bool debug = false;
+
+ main() {
+ for(int i = 0; i < 50000; i++) {
+ foo();
+ }
+ debug = true;
+ foo();
+ }
+
+ foo() {
+ List<int> data = [1, 2, 3];
+ for(int i in data) {
+ if (bar(i)) {
+ break;
+ }
+ }
+ }
+
+ bool bar(int i) {
+ if (i == 2) {
+ if (debug) {
+ debugger();
+ print("set a breakpoint here!");
+ print("woke up");
+ }
+ return true;
+ }
+ return false;
+ }
+
+definitions: []
+definition_types: []
+type_definitions: []
+type_bounds: []
+type_defaults: []
+method: "foo"
+static: true
+offset: 219
+scriptUri: main.dart
+expression: |
+ data = 42
diff --git a/pkg/front_end/testcases/expression/issue_53996_03.expression.yaml.expect b/pkg/front_end/testcases/expression/issue_53996_03.expression.yaml.expect
new file mode 100644
index 0000000..0960c17
--- /dev/null
+++ b/pkg/front_end/testcases/expression/issue_53996_03.expression.yaml.expect
@@ -0,0 +1,7 @@
+Errors: {
+ org-dartlang-debug:synthetic_debug_expression:1:1: Error: The variable 'data' is unavailable in this expression evaluation.
+ data = 42
+ ^^^^
+}
+static method /* from org-dartlang-debug:synthetic_debug_expression */ debugExpr() → dynamic
+ return invalid-expression "org-dartlang-debug:synthetic_debug_expression:1:1: Error: The variable 'data' is unavailable in this expression evaluation.\ndata = 42\n^^^^";
diff --git a/pkg/front_end/testcases/expression/issue_53996_04.expression.yaml b/pkg/front_end/testcases/expression/issue_53996_04.expression.yaml
new file mode 100644
index 0000000..0dab58f
--- /dev/null
+++ b/pkg/front_end/testcases/expression/issue_53996_04.expression.yaml
@@ -0,0 +1,62 @@
+# 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.
+
+# https://github.com/dart-lang/sdk/issues/53996
+
+# Definition, offset, method etc extracted by starting the VM with
+# `-DDFE_VERBOSE=true`, e.g.
+# ```
+# out/ReleaseX64/dart -DDFE_VERBOSE=true --enable-vm-service \
+# --disable-service-auth-codes --pause_isolates_on_start inputFile.dart
+# ```
+# and then issuing the expression compilation.
+
+sources: |
+ import "dart:developer";
+
+ bool debug = false;
+
+ String data = "hello from top level field";
+
+ main() {
+ for(int i = 0; i < 50000; i++) {
+ foo();
+ }
+ debug = true;
+ foo();
+ print(data);
+ }
+
+ foo() {
+ List<int> data = [1, 2, 3];
+ for(int i in data) {
+ if (bar(i)) {
+ break;
+ }
+ }
+ }
+
+ bool bar(int i) {
+ if (i == 2) {
+ if (debug) {
+ debugger();
+ print("set a breakpoint here!");
+ print("woke up");
+ }
+ return true;
+ }
+ return false;
+ }
+
+definitions: []
+definition_types: []
+type_definitions: []
+type_bounds: []
+type_defaults: []
+method: "foo"
+static: true
+offset: 279
+scriptUri: main.dart
+expression: |
+ data = 42
diff --git a/pkg/front_end/testcases/expression/issue_53996_04.expression.yaml.expect b/pkg/front_end/testcases/expression/issue_53996_04.expression.yaml.expect
new file mode 100644
index 0000000..0960c17
--- /dev/null
+++ b/pkg/front_end/testcases/expression/issue_53996_04.expression.yaml.expect
@@ -0,0 +1,7 @@
+Errors: {
+ org-dartlang-debug:synthetic_debug_expression:1:1: Error: The variable 'data' is unavailable in this expression evaluation.
+ data = 42
+ ^^^^
+}
+static method /* from org-dartlang-debug:synthetic_debug_expression */ debugExpr() → dynamic
+ return invalid-expression "org-dartlang-debug:synthetic_debug_expression:1:1: Error: The variable 'data' is unavailable in this expression evaluation.\ndata = 42\n^^^^";
diff --git a/pkg/front_end/testcases/expression/issue_53996_05.expression.yaml b/pkg/front_end/testcases/expression/issue_53996_05.expression.yaml
new file mode 100644
index 0000000..c145cdb
--- /dev/null
+++ b/pkg/front_end/testcases/expression/issue_53996_05.expression.yaml
@@ -0,0 +1,42 @@
+# 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.
+
+# https://github.com/dart-lang/sdk/issues/53996
+
+# Definition, offset, method etc extracted by starting the VM with
+# `-DDFE_VERBOSE=true`, e.g.
+# ```
+# out/ReleaseX64/dart -DDFE_VERBOSE=true --enable-vm-service \
+# --disable-service-auth-codes --pause_isolates_on_start inputFile.dart
+# ```
+# and then issuing the expression compilation.
+
+sources: |
+ import "dart:developer";
+
+ class A {
+ List list;
+ A(this.list) {
+ debugger();
+ list = [3];
+ print(list);
+ }
+ }
+
+ void main() {
+ new A([1, 2]);
+ }
+
+definitions: []
+definition_types: []
+type_definitions: []
+type_bounds: []
+type_defaults: []
+method: "A"
+position: "#A"
+static: false
+offset: 70
+scriptUri: main.dart
+expression: |
+ list
diff --git a/pkg/front_end/testcases/expression/issue_53996_05.expression.yaml.expect b/pkg/front_end/testcases/expression/issue_53996_05.expression.yaml.expect
new file mode 100644
index 0000000..f0822b3
--- /dev/null
+++ b/pkg/front_end/testcases/expression/issue_53996_05.expression.yaml.expect
@@ -0,0 +1,4 @@
+Errors: {
+}
+method /* from org-dartlang-debug:synthetic_debug_expression */ debugExpr() → dynamic
+ return this.{#lib1::A::list}{dart.core::List<dynamic>};
diff --git a/pkg/front_end/testcases/expression/issue_53996_06.expression.yaml b/pkg/front_end/testcases/expression/issue_53996_06.expression.yaml
new file mode 100644
index 0000000..8ada9e3
--- /dev/null
+++ b/pkg/front_end/testcases/expression/issue_53996_06.expression.yaml
@@ -0,0 +1,42 @@
+# 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.
+
+# https://github.com/dart-lang/sdk/issues/53996
+
+# Definition, offset, method etc extracted by starting the VM with
+# `-DDFE_VERBOSE=true`, e.g.
+# ```
+# out/ReleaseX64/dart -DDFE_VERBOSE=true --enable-vm-service \
+# --disable-service-auth-codes --pause_isolates_on_start inputFile.dart
+# ```
+# and then issuing the expression compilation.
+
+sources: |
+ import "dart:developer";
+
+ class A {
+ List list;
+ A.named(this.list) {
+ debugger();
+ list = [3];
+ print(list);
+ }
+ }
+
+ void main() {
+ new A.named([1, 2]);
+ }
+
+definitions: []
+definition_types: []
+type_definitions: []
+type_bounds: []
+type_defaults: []
+method: "A.named"
+position: "#A"
+static: false
+offset: 76
+scriptUri: main.dart
+expression: |
+ list
diff --git a/pkg/front_end/testcases/expression/issue_53996_06.expression.yaml.expect b/pkg/front_end/testcases/expression/issue_53996_06.expression.yaml.expect
new file mode 100644
index 0000000..f0822b3
--- /dev/null
+++ b/pkg/front_end/testcases/expression/issue_53996_06.expression.yaml.expect
@@ -0,0 +1,4 @@
+Errors: {
+}
+method /* from org-dartlang-debug:synthetic_debug_expression */ debugExpr() → dynamic
+ return this.{#lib1::A::list}{dart.core::List<dynamic>};
diff --git a/pkg/front_end/testcases/expression/issue_53996_07.expression.yaml b/pkg/front_end/testcases/expression/issue_53996_07.expression.yaml
new file mode 100644
index 0000000..49e5b97
--- /dev/null
+++ b/pkg/front_end/testcases/expression/issue_53996_07.expression.yaml
@@ -0,0 +1,48 @@
+# 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.
+
+# https://github.com/dart-lang/sdk/issues/53996
+
+# Definition, offset, method etc extracted by starting the VM with
+# `-DDFE_VERBOSE=true`, e.g.
+# ```
+# out/ReleaseX64/dart -DDFE_VERBOSE=true --enable-vm-service \
+# --disable-service-auth-codes --pause_isolates_on_start inputFile.dart
+# ```
+# and then issuing the expression compilation.
+
+sources: |
+ import "dart:developer";
+
+ class A {
+ List list;
+ A(this.list) {
+ list = [3];
+ }
+ }
+
+ class B extends A {
+ B(super.list) {
+ debugger();
+ list = [7];
+ print(list);
+ }
+ }
+
+ void main() {
+ new B([1, 2]);
+ }
+
+definitions: []
+definition_types: []
+type_definitions: []
+type_bounds: []
+type_defaults: []
+method: "B"
+position: "#B"
+static: false
+offset: 131
+scriptUri: main.dart
+expression: |
+ list
diff --git a/pkg/front_end/testcases/expression/issue_53996_07.expression.yaml.expect b/pkg/front_end/testcases/expression/issue_53996_07.expression.yaml.expect
new file mode 100644
index 0000000..f0822b3
--- /dev/null
+++ b/pkg/front_end/testcases/expression/issue_53996_07.expression.yaml.expect
@@ -0,0 +1,4 @@
+Errors: {
+}
+method /* from org-dartlang-debug:synthetic_debug_expression */ debugExpr() → dynamic
+ return this.{#lib1::A::list}{dart.core::List<dynamic>};
diff --git a/pkg/vm_service/test/evaluate_optimized_out_variable_test.dart b/pkg/vm_service/test/evaluate_optimized_out_variable_test.dart
index 10d87c3..0f96c06 100644
--- a/pkg/vm_service/test/evaluate_optimized_out_variable_test.dart
+++ b/pkg/vm_service/test/evaluate_optimized_out_variable_test.dart
@@ -67,7 +67,11 @@
} on RPCError catch (e) {
expect(e.code, RPCErrorKind.kExpressionCompilationError.code);
expect(e.message, 'Expression compilation error');
- expect(e.details, contains("Error: Undefined name 'data'."));
+ expect(
+ e.details,
+ contains("Error: The variable 'data' "
+ 'is unavailable in this expression evaluation.'),
+ );
}
},
];
diff --git a/pkg/vm_service/test/evaluate_uninitialized_late_variable_test.dart b/pkg/vm_service/test/evaluate_uninitialized_late_variable_test.dart
index 7f111b1..1c72407 100644
--- a/pkg/vm_service/test/evaluate_uninitialized_late_variable_test.dart
+++ b/pkg/vm_service/test/evaluate_uninitialized_late_variable_test.dart
@@ -45,7 +45,11 @@
// solution.
expect(e.code, RPCErrorKind.kExpressionCompilationError.code);
expect(e.message, 'Expression compilation error');
- expect(e.details, contains("Error: Undefined name 'x'."));
+ expect(
+ e.details,
+ contains("Error: The variable 'x' "
+ 'is unavailable in this expression evaluation.'),
+ );
}
},
];