[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.'),
+      );
     }
   },
 ];