[CFE] Fix crash in expression compilation when evaluating inside extension type with type parameter

E.g. expression evaluation threw when serializing when trying to
evaluate inside `write` on
`extension type EnumSet<T extends Enum>(int _bits)` from the analyzer.

Change-Id: I4738b37c5431f11b11abff0e8d7183514a66b5dd
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/459441
Commit-Queue: Jens Johansen <jensj@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/front_end/lib/src/base/incremental_compiler.dart b/pkg/front_end/lib/src/base/incremental_compiler.dart
index 1cdc458..b41bfe7 100644
--- a/pkg/front_end/lib/src/base/incremental_compiler.dart
+++ b/pkg/front_end/lib/src/base/incremental_compiler.dart
@@ -1836,6 +1836,7 @@
       LibraryBuilder libraryBuilder = compilationUnit.libraryBuilder;
       List<VariableDeclarationImpl> extraKnownVariables = [];
       String? usedMethodName = methodName;
+      Substitution? substitution;
       if (scriptUri != null && offset != TreeNode.noOffset) {
         Uri? scriptUriAsUri = Uri.tryParse(scriptUri);
         if (scriptUriAsUri != null) {
@@ -1876,23 +1877,11 @@
             );
           }
 
-          Map<TypeParameter, TypeParameterType> substitutionMap = {};
-          Map<String, TypeParameter> typeDefinitionNamesMap = {};
-          for (TypeParameter typeDefinition in typeDefinitions) {
-            if (typeDefinition.name != null) {
-              typeDefinitionNamesMap[typeDefinition.name!] = typeDefinition;
-            }
-          }
-          for (TypeParameter typeParameter in foundScope.typeParameters) {
-            TypeParameter? match = typeDefinitionNamesMap[typeParameter.name];
-            if (match != null) {
-              substitutionMap[typeParameter] = new TypeParameterType(
-                match,
-                match.computeNullabilityFromBound(),
+          substitution =
+              _calculateExpressionEvaluationTypeParameterSubstitution(
+                typeDefinitions,
+                foundScope.typeParameters,
               );
-            }
-          }
-          Substitution substitution = Substitution.fromMap(substitutionMap);
 
           final bool alwaysInlineConstants = lastGoodKernelTarget
               .backendTarget
@@ -2034,7 +2023,19 @@
                   // If we setup the extensionType (and later the
                   // `extensionThis`) we should also set the type correctly
                   // (at least in a non-static setting).
-                  usedDefinitions[syntheticThisName] = positionals.first.type;
+                  if (substitution == null || substitution.isEmpty) {
+                    // Re-do substitutions if the old one is empty - in case the
+                    // finding of scope didn't find the right thing (e.g.
+                    // sometimes the VM claims to be on the offset for a method
+                    // name while having data as if it is inside the method).
+                    substitution =
+                        _calculateExpressionEvaluationTypeParameterSubstitution(
+                          typeDefinitions,
+                          subBuilder.invokeTarget?.function?.typeParameters,
+                        );
+                  }
+                  usedDefinitions[syntheticThisName] = substitution
+                      .substituteType(positionals.first.type);
                 }
                 isStatic = false;
               }
@@ -2268,6 +2269,32 @@
   }
 
   // Coverage-ignore(suite): Not run.
+  Substitution _calculateExpressionEvaluationTypeParameterSubstitution(
+    List<TypeParameter> typeDefinitions,
+    List<TypeParameter>? typeParameters,
+  ) {
+    Map<TypeParameter, TypeParameterType> substitutionMap = {};
+    Map<String, TypeParameter> typeDefinitionNamesMap = {};
+    for (TypeParameter typeDefinition in typeDefinitions) {
+      if (typeDefinition.name != null) {
+        typeDefinitionNamesMap[typeDefinition.name!] = typeDefinition;
+      }
+    }
+    if (typeParameters != null) {
+      for (TypeParameter typeParameter in typeParameters) {
+        TypeParameter? match = typeDefinitionNamesMap[typeParameter.name];
+        if (match != null) {
+          substitutionMap[typeParameter] = new TypeParameterType(
+            match,
+            match.computeNullabilityFromBound(),
+          );
+        }
+      }
+    }
+    return Substitution.fromMap(substitutionMap);
+  }
+
+  // Coverage-ignore(suite): Not run.
   bool _packagesEqual(Package? a, Package? b) {
     if (a == null || b == null) return false;
     if (a.name != b.name) return false;
diff --git a/pkg/front_end/testcases/expression/extension_type_with_type_parameter.expression.yaml b/pkg/front_end/testcases/expression/extension_type_with_type_parameter.expression.yaml
new file mode 100644
index 0000000..64636d3
--- /dev/null
+++ b/pkg/front_end/testcases/expression/extension_type_with_type_parameter.expression.yaml
@@ -0,0 +1,43 @@
+# 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.
+
+# Definition, offset, method etc extracted by starting the VM with
+# `-DDFE_VERBOSE=true`, e.g.
+# ```
+# out/ReleaseX64/dart -DDFE_VERBOSE=true --enable-vm-service inputFile.dart
+# ```
+# and then issuing the expression compilation.
+#
+# Inside an extension type with type parameters - shouldn't crash on
+# serialization.
+
+sources:
+  main.dart: |
+    import "dart:developer";
+
+    extension type Foo<T extends Enum>(int foo) {
+      void bar(T constant) {
+        debugger();
+        print(constant.index);
+      }
+    }
+
+    enum Bar { bar, baz }
+
+    void main() {
+      var x = Foo<Bar>(0);
+      x.bar(Bar.bar);
+    }
+
+definitions: ["#this", "constant"]
+definition_types: ["dart:core", "_Smi", "1", "0", "file:///usr/local/google/home/jensj/code/dart-sdk/sdk/t.dart", "Bar", "1", "0"]
+type_definitions: ["T"]
+type_bounds: ["dart:core", "Enum", "1", "0"]
+type_defaults: ["dart:core", "Enum", "1", "0"]
+method: "Foo.bar"
+static: true
+offset: 105
+scriptUri: main.dart
+expression: |
+ constant.index
diff --git a/pkg/front_end/testcases/expression/extension_type_with_type_parameter.expression.yaml.expect b/pkg/front_end/testcases/expression/extension_type_with_type_parameter.expression.yaml.expect
new file mode 100644
index 0000000..2ec96a3
--- /dev/null
+++ b/pkg/front_end/testcases/expression/extension_type_with_type_parameter.expression.yaml.expect
@@ -0,0 +1,4 @@
+Errors: {
+}
+method /* from org-dartlang-debug:synthetic_debug_expression */ debugExpr<T extends dart.core::Enum>(lowered #lib1::Foo<#lib2::debugExpr::T>% /* erasure=dart.core::int, declared=! */ #this, #lib2::debugExpr::T constant) → dynamic
+  return constant.{dart.core::Enum::index}{dart.core::int};
diff --git a/pkg/front_end/testcases/expression/issue_56911_03.expression.yaml b/pkg/front_end/testcases/expression/issue_56911_03.expression.yaml
new file mode 100644
index 0000000..81893f1
--- /dev/null
+++ b/pkg/front_end/testcases/expression/issue_56911_03.expression.yaml
@@ -0,0 +1,35 @@
+# Copyright (c) 2024, 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/56911
+
+sources: |
+  void main() {
+    List<ExtensionType> list = [new ExtensionType(0)];
+    list.forEach((ExtensionType input) {
+      print(input.value);
+    });
+  }
+
+  extension type ExtensionType._(String s) {
+    ExtensionType(int i) : this._("$i");
+    int get value => s.codeUnitAt(0);
+  }
+
+definitions: ["#this"]
+# String
+definition_types: ["dart:core", "_OneByteString", "1", "0"]
+type_definitions: []
+type_bounds: []
+type_defaults: []
+method: "ExtensionType.value"
+static: true
+offset: 231 # at the 'value' of 'int get value => s.codeUnitAt(0);' line.
+scriptUri: main.dart
+expression: |
+  () {
+    s;
+    s.codeUnitAt(0);
+    value;
+  }()
diff --git a/pkg/front_end/testcases/expression/issue_56911_03.expression.yaml.expect b/pkg/front_end/testcases/expression/issue_56911_03.expression.yaml.expect
new file mode 100644
index 0000000..4aaaffe
--- /dev/null
+++ b/pkg/front_end/testcases/expression/issue_56911_03.expression.yaml.expect
@@ -0,0 +1,8 @@
+Errors: {
+}
+method /* from org-dartlang-debug:synthetic_debug_expression */ debugExpr(lowered #lib1::ExtensionType% /* erasure=dart.core::String, declared=! */ #this) → dynamic
+  return (() → Null {
+    #this as{Unchecked} dart.core::String;
+    (#this as{Unchecked} dart.core::String).{dart.core::String::codeUnitAt}(0){(dart.core::int) → dart.core::int};
+    #lib1::ExtensionType|get#value(#this);
+  })(){() → Null};
diff --git a/pkg/front_end/testcases/expression/issue_56911_04.expression.yaml b/pkg/front_end/testcases/expression/issue_56911_04.expression.yaml
new file mode 100644
index 0000000..621f37f
--- /dev/null
+++ b/pkg/front_end/testcases/expression/issue_56911_04.expression.yaml
@@ -0,0 +1,34 @@
+# Copyright (c) 2024, 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/56911
+
+sources: |
+  void main() {
+    List<ExtensionType> list = [new ExtensionType("0")];
+    list.forEach((ExtensionType input) {
+      print(input.value);
+    });
+  }
+
+  extension type ExtensionType<E extends String>(E e) {
+    int get value => e.codeUnitAt(0);
+  }
+
+definitions: ["#this"]
+# String
+definition_types: ["dart:core", "_OneByteString", "1", "0"]
+type_definitions: ["E"]
+type_bounds: ["dart:core", "String", "1", "0"]
+type_defaults: ["dart:core", "String", "1", "0"]
+method: "ExtensionType.value"
+static: true
+offset: 205 # at the 'value' of 'int get value => s.codeUnitAt(0);' line.
+scriptUri: main.dart
+expression: |
+  () {
+    s;
+    s.codeUnitAt(0);
+    value;
+  }()
diff --git a/pkg/front_end/testcases/expression/issue_56911_04.expression.yaml.expect b/pkg/front_end/testcases/expression/issue_56911_04.expression.yaml.expect
new file mode 100644
index 0000000..14b3383
--- /dev/null
+++ b/pkg/front_end/testcases/expression/issue_56911_04.expression.yaml.expect
@@ -0,0 +1,8 @@
+Errors: {
+}
+method /* from org-dartlang-debug:synthetic_debug_expression */ debugExpr<E extends dart.core::String>(lowered #lib1::ExtensionType<#lib2::debugExpr::E>% /* erasure=#lib2::debugExpr::E, declared=! */ #this) → dynamic
+  return (() → Null {
+    #this{dynamic}.s;
+    #this{dynamic}.s{dynamic}.codeUnitAt(0);
+    #lib1::ExtensionType|get#value<#lib2::debugExpr::E>(#this);
+  })(){() → Null};