[kernel/DDC] Fix failing DDC tests; if finding no scope for offset find for a close previous offset instead

When landing https://dart-review.googlesource.com/c/sdk/+/342400
the ddc-mac builder (but not Linux and Windows) started failing.
I could reproduce on Linux though so I'm not sure why those builders
didn't fail (nor why it wasn't caught by the try bot), either way this
fixes the issues:

* Test update: E.g. a breakpoint at the end of the scope doesn't work
  because it will not be inside the (wanted) scope.
* Test update: Evalating "this" now actually works.
* Scope finding update: DDC adds sourcemapping entries for the *end* of
  things so e.g. the getter "c" will have offset and offset+1 added as
  source mappings. When translating from javascript position to dart
  position we might pick that and thus ask for the scope of offset+1,
  but nothing will be found because no node has that offset.
  Here I add a fall-back saying that if we get no results we ask again
  for the nearest lower offset.

Change-Id: I7e4430d9954466494b514cf51d999358483c9f8c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/345501
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Jens Johansen <jensj@google.com>
Reviewed-by: Anna Gringauze <annagrin@google.com>
diff --git a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_shared.dart b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_shared.dart
index 2310cdf..0a35a3d 100644
--- a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_shared.dart
+++ b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_shared.dart
@@ -806,6 +806,7 @@
           List<int> list = [];
           list.add(0);
           // Breakpoint: bp
+          print(list);
         }
         ''';
 
@@ -854,7 +855,7 @@
       await driver.checkInFrame(
           breakpointId: 'bp',
           expression: 'typo',
-          expectedError: "Error: Undefined name 'typo'");
+          expectedError: "Error: The getter 'typo' isn't defined");
     });
 
     test('local (trimmed scope)', () async {
@@ -863,13 +864,8 @@
     });
 
     test('this (full scope)', () async {
-      // Note: this currently fails due to
-      // - incremental compiler not mapping 'this' from user input to '#this'
-      // - incremental compiler not allowing #this as a parameter name
       await driver.checkInFrame(
-          breakpointId: 'bp',
-          expression: 'this',
-          expectedError: "Error: Expected identifier, but got 'this'");
+          breakpointId: 'bp', expression: 'this', expectedResult: '1234');
     });
 
     test('scope', () async {
diff --git a/pkg/kernel/lib/dart_scope_calculator.dart b/pkg/kernel/lib/dart_scope_calculator.dart
index 8b645eb..8d46f0e 100644
--- a/pkg/kernel/lib/dart_scope_calculator.dart
+++ b/pkg/kernel/lib/dart_scope_calculator.dart
@@ -262,6 +262,7 @@
   final Uri _scriptUri;
   final int _offset;
   final List<DartScope2> findScopes = [];
+  final Set<int> foundOffsets = {};
 
   final Set<VariableDeclaration> hoistedUnwritten = {};
   final List<List<VariableDeclaration>> scopes = [];
@@ -537,12 +538,14 @@
 
   void _checkOffset(TreeNode node) {
     if (_currentUri == _scriptUri) {
+      foundOffsets.add(node.fileOffset);
       if (node.fileOffset == _offset) {
         addFound(node);
       } else {
         List<int>? allOffsets = node.fileOffsetsIfMultiple;
         if (allOffsets != null) {
           for (final int offset in allOffsets) {
+            foundOffsets.add(offset);
             if (offset == _offset) {
               addFound(node);
               break;
@@ -555,8 +558,36 @@
 
   static DartScope findScopeFromOffsetAndClass(
       Library library, Uri scriptUri, Class? cls, int offset) {
-    List<DartScope2> scopes = _raw(library, scriptUri, cls, offset);
-    return _findScopePick(scopes, library, cls, offset);
+    DartScopeBuilder2 data = _raw(library, scriptUri, cls, offset);
+    if (data.findScopes.isEmpty) {
+      int? closestMatchingOrSmallerOffset =
+          _findClosestMatchingOrSmallerOffset(data, offset);
+      if (closestMatchingOrSmallerOffset != null) {
+        offset = closestMatchingOrSmallerOffset;
+        data = _raw(library, scriptUri, cls, offset);
+      }
+    }
+    return _findScopePick(data.findScopes, library, cls, offset);
+  }
+
+  static int? _findClosestMatchingOrSmallerOffset(
+      DartScopeBuilder2 data, int offset) {
+    List<int> foundOffsets = data.foundOffsets.toList()..sort();
+    if (foundOffsets.isEmpty) return null;
+    int low = 0;
+    int high = foundOffsets.length - 1;
+    while (low < high) {
+      int mid = high - ((high - low) >> 1); // Get middle, rounding up.
+      int pivot = foundOffsets[mid];
+      if (pivot <= offset) {
+        low = mid;
+      } else {
+        high = mid - 1;
+      }
+    }
+    int result = foundOffsets[low];
+    if (result < 0) return null;
+    return result;
   }
 
   static DartScope _findScopePick(
@@ -593,8 +624,16 @@
 
   static DartScope findScopeFromOffset(
       Library library, Uri scriptUri, int offset) {
-    List<DartScope2> scopes = _rawNoClass(library, scriptUri, offset);
-    return _findScopePick(scopes, library, null, offset);
+    DartScopeBuilder2 data = _rawNoClass(library, scriptUri, offset);
+    if (data.findScopes.isEmpty) {
+      int? closestMatchingOrSmallerOffset =
+          _findClosestMatchingOrSmallerOffset(data, offset);
+      if (closestMatchingOrSmallerOffset != null) {
+        offset = closestMatchingOrSmallerOffset;
+        data = _rawNoClass(library, scriptUri, offset);
+      }
+    }
+    return _findScopePick(data.findScopes, library, null, offset);
   }
 
   static List<DartScope2> _filterAll(
@@ -1153,7 +1192,7 @@
     return null;
   }
 
-  static List<DartScope2> _raw(
+  static DartScopeBuilder2 _raw(
       Library library, Uri scriptUri, Class? cls, int offset) {
     DartScopeBuilder2 builder = DartScopeBuilder2._(library, scriptUri, offset);
     if (cls != null) {
@@ -1163,17 +1202,17 @@
       builder.visitLibrary(library);
     }
 
-    return builder.findScopes;
+    return builder;
   }
 
-  static List<DartScope2> _rawNoClass(
+  static DartScopeBuilder2 _rawNoClass(
       Library library, Uri scriptUri, int offset) {
     DartScopeBuilder2 builder = DartScopeBuilder2._(library, scriptUri, offset);
     builder.visitLibrary(library);
-    return builder.findScopes;
+    return builder;
   }
 
   static List<DartScope2> findScopeFromOffsetAndClassRawForTesting(
           Library library, Uri scriptUri, Class? cls, int offset) =>
-      _raw(library, scriptUri, cls, offset);
+      _raw(library, scriptUri, cls, offset).findScopes;
 }