[record_use] Record instances in calls

So that calls with const instances as arguments can be recorded.

Tested: pkg/vm/testcases/transformations/record_use/*
Change-Id: I918e7486b2772bb6d014abb9f01f4347e24ac0b1
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/436300
Commit-Queue: Moritz Sümmermann <mosum@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
diff --git a/pkg/vm/lib/transformations/record_use/record_instance.dart b/pkg/vm/lib/transformations/record_use/record_instance.dart
index 35c0229..7287e08 100644
--- a/pkg/vm/lib/transformations/record_use/record_instance.dart
+++ b/pkg/vm/lib/transformations/record_use/record_instance.dart
@@ -81,15 +81,7 @@
     ast.InstanceConstant constant,
   ) => InstanceReference(
     location: expression.location!.recordLocation(_source, exactLocation),
-    instanceConstant: _fieldsFromConstant(constant),
+    instanceConstant: evaluateInstanceConstant(constant),
     loadingUnit: loadingUnitForNode(expression, _loadingUnits).toString(),
   );
-
-  InstanceConstant _fieldsFromConstant(ast.InstanceConstant constant) =>
-      InstanceConstant(
-        fields: constant.fieldValues.map(
-          (key, value) =>
-              MapEntry(key.asField.name.text, evaluateConstant(value)),
-        ),
-      );
 }
diff --git a/pkg/vm/lib/transformations/record_use/record_use.dart b/pkg/vm/lib/transformations/record_use/record_use.dart
index b58e957..9f8c6a9 100644
--- a/pkg/vm/lib/transformations/record_use/record_use.dart
+++ b/pkg/vm/lib/transformations/record_use/record_use.dart
@@ -129,9 +129,9 @@
   ast.ListConstant() => ListConstant(
     constant.entries.map(evaluateConstant).toList(),
   ),
+  ast.InstanceConstant() => evaluateInstanceConstant(constant),
   // The following are not supported, but theoretically could be, so they
   // are listed explicitly here.
-  ast.InstanceConstant() => _unsupported('InstanceConstant'),
   ast.AuxiliaryConstant() => _unsupported('AuxiliaryConstant'),
   ast.SetConstant() => _unsupported('SetConstant'),
   ast.RecordConstant() => _unsupported('RecordConstant'),
@@ -151,6 +151,14 @@
   ast.BasicLiteral() => _unsupported(expression.runtimeType.toString()),
 };
 
+InstanceConstant evaluateInstanceConstant(ast.InstanceConstant constant) =>
+    InstanceConstant(
+      fields: constant.fieldValues.map(
+        (key, value) =>
+            MapEntry(key.asField.name.text, evaluateConstant(value)),
+      ),
+    );
+
 Never _unsupported(String constantType) =>
     throw UnsupportedError('$constantType is not supported for recording.');
 
diff --git a/pkg/vm/testcases/transformations/record_use/record_enum.dart b/pkg/vm/testcases/transformations/record_use/record_enum.dart
new file mode 100644
index 0000000..fbe3694
--- /dev/null
+++ b/pkg/vm/testcases/transformations/record_use/record_enum.dart
@@ -0,0 +1,23 @@
+// 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.
+
+import 'package:meta/meta.dart' show RecordUse;
+
+void main() {
+  doSomething();
+}
+
+@MyClass(A.a)
+void doSomething() {
+  print('a');
+}
+
+@RecordUse()
+class MyClass {
+  final A a;
+
+  const MyClass(this.a);
+}
+
+enum A { a, b }
diff --git a/pkg/vm/testcases/transformations/record_use/record_enum.dart.aot.expect b/pkg/vm/testcases/transformations/record_use/record_enum.dart.aot.expect
new file mode 100644
index 0000000..3ff4659
--- /dev/null
+++ b/pkg/vm/testcases/transformations/record_use/record_enum.dart.aot.expect
@@ -0,0 +1,39 @@
+library #lib;
+import self as self;
+import "package:meta/meta.dart" as meta;
+import "dart:core" as core;
+
+import "package:meta/meta.dart" show RecordUse;
+
+@#C1
+class MyClass extends core::Object /*hasConstConstructor*/  {
+
+  [@vm.inferred-type.metadata=#lib::A (value: const #lib::A{dart.core::_Enum.index: 0, dart.core::_Enum._name: "a"})]
+  [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:1]
+  final field self::A a;
+}
+class A extends core::_Enum /*isEnum*/  {
+
+  [@vm.inferred-return-type.metadata=!]
+  [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:2,getterSelectorId:3]
+  method core::_enumToString() → core::String
+    return "A.${[@vm.direct-call.metadata=dart.core::_Enum._name] this.{core::_Enum::_name}{core::String}}";
+}
+
+[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
+static method main() → void {
+  self::doSomething();
+}
+
+[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
+@#C5
+static method doSomething() → void {
+  core::print("a");
+}
+constants  {
+  #C1 = meta::RecordUse {}
+  #C2 = 0
+  #C3 = "a"
+  #C4 = self::A {index:#C2, _name:#C3}
+  #C5 = self::MyClass {a:#C4}
+}
diff --git a/pkg/vm/testcases/transformations/record_use/record_enum.dart.json.expect b/pkg/vm/testcases/transformations/record_use/record_enum.dart.json.expect
new file mode 100644
index 0000000..3d66bb8
--- /dev/null
+++ b/pkg/vm/testcases/transformations/record_use/record_enum.dart.json.expect
@@ -0,0 +1,52 @@
+{
+  "metadata": {
+    "comment": "Recorded usages of objects tagged with a `RecordUse` annotation",
+    "version": "0.2.0"
+  },
+  "constants": [
+    {
+      "type": "int",
+      "value": 0
+    },
+    {
+      "type": "String",
+      "value": "a"
+    },
+    {
+      "type": "Instance",
+      "value": {
+        "index": 0,
+        "_name": 1
+      }
+    },
+    {
+      "type": "Instance",
+      "value": {
+        "a": 2
+      }
+    }
+  ],
+  "locations": [
+    {
+      "uri": "record_enum.dart"
+    }
+  ],
+  "recordings": [
+    {
+      "definition": {
+        "identifier": {
+          "uri": "record_enum.dart",
+          "name": "MyClass"
+        },
+        "loading_unit": "1"
+      },
+      "instances": [
+        {
+          "constant_index": 3,
+          "loading_unit": "1",
+          "@": 0
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/pkg/vm/testcases/transformations/record_use/record_instance_constant.dart.aot.expect b/pkg/vm/testcases/transformations/record_use/record_instance_constant.dart.aot.expect
new file mode 100644
index 0000000..25d53475
--- /dev/null
+++ b/pkg/vm/testcases/transformations/record_use/record_instance_constant.dart.aot.expect
@@ -0,0 +1,38 @@
+library #lib;
+import self as self;
+import "package:meta/meta.dart" as meta;
+import "dart:core" as core;
+
+import "package:meta/meta.dart" show RecordUse;
+
+@#C1
+class MyClass extends core::Object /*hasConstConstructor*/  {
+
+  [@vm.inferred-type.metadata=#lib::A (value: const #lib::A{#lib::A.i: 42})]
+  [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:1]
+  final field self::A a;
+}
+class A extends core::Object /*hasConstConstructor*/  {
+
+  [@vm.inferred-type.metadata=dart.core::_Smi (value: 42)]
+  [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:2]
+  [@vm.unboxing-info.metadata=()->i]
+  final field core::int i;
+}
+
+[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
+static method main() → void {
+  self::doSomething();
+}
+
+[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
+@#C4
+static method doSomething() → void {
+  core::print("a");
+}
+constants  {
+  #C1 = meta::RecordUse {}
+  #C2 = 42
+  #C3 = self::A {i:#C2}
+  #C4 = self::MyClass {a:#C3}
+}
diff --git a/pkg/vm/testcases/transformations/record_use/record_instance_constant.dart.json.expect b/pkg/vm/testcases/transformations/record_use/record_instance_constant.dart.json.expect
new file mode 100644
index 0000000..8014626
--- /dev/null
+++ b/pkg/vm/testcases/transformations/record_use/record_instance_constant.dart.json.expect
@@ -0,0 +1,47 @@
+{
+  "metadata": {
+    "comment": "Recorded usages of objects tagged with a `RecordUse` annotation",
+    "version": "0.2.0"
+  },
+  "constants": [
+    {
+      "type": "int",
+      "value": 42
+    },
+    {
+      "type": "Instance",
+      "value": {
+        "i": 0
+      }
+    },
+    {
+      "type": "Instance",
+      "value": {
+        "a": 1
+      }
+    }
+  ],
+  "locations": [
+    {
+      "uri": "record_instance_constant.dart"
+    }
+  ],
+  "recordings": [
+    {
+      "definition": {
+        "identifier": {
+          "uri": "record_instance_constant.dart",
+          "name": "MyClass"
+        },
+        "loading_unit": "1"
+      },
+      "instances": [
+        {
+          "constant_index": 2,
+          "loading_unit": "1",
+          "@": 0
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/pkg/vm/testcases/transformations/record_use/record_instance_constant_empty.dart b/pkg/vm/testcases/transformations/record_use/record_instance_constant_empty.dart
new file mode 100644
index 0000000..70de329
--- /dev/null
+++ b/pkg/vm/testcases/transformations/record_use/record_instance_constant_empty.dart
@@ -0,0 +1,25 @@
+// 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.
+
+import 'package:meta/meta.dart' show RecordUse;
+
+void main() {
+  doSomething();
+}
+
+@MyClass(const A())
+void doSomething() {
+  print('a');
+}
+
+@RecordUse()
+class MyClass {
+  final A a;
+
+  const MyClass(this.a);
+}
+
+class A {
+  const A();
+}
diff --git a/pkg/vm/testcases/transformations/record_use/record_instance_constant_empty.dart.aot.expect b/pkg/vm/testcases/transformations/record_use/record_instance_constant_empty.dart.aot.expect
new file mode 100644
index 0000000..cdc4c90
--- /dev/null
+++ b/pkg/vm/testcases/transformations/record_use/record_instance_constant_empty.dart.aot.expect
@@ -0,0 +1,32 @@
+library #lib;
+import self as self;
+import "package:meta/meta.dart" as meta;
+import "dart:core" as core;
+
+import "package:meta/meta.dart" show RecordUse;
+
+@#C1
+class MyClass extends core::Object /*hasConstConstructor*/  {
+
+  [@vm.inferred-type.metadata=#lib::A (value: const #lib::A{})]
+  [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:1]
+  final field self::A a;
+}
+class A extends core::Object /*hasConstConstructor*/  {
+}
+
+[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
+static method main() → void {
+  self::doSomething();
+}
+
+[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
+@#C3
+static method doSomething() → void {
+  core::print("a");
+}
+constants  {
+  #C1 = meta::RecordUse {}
+  #C2 = self::A {}
+  #C3 = self::MyClass {a:#C2}
+}
diff --git a/pkg/vm/testcases/transformations/record_use/record_instance_constant_empty.dart.json.expect b/pkg/vm/testcases/transformations/record_use/record_instance_constant_empty.dart.json.expect
new file mode 100644
index 0000000..861020d
--- /dev/null
+++ b/pkg/vm/testcases/transformations/record_use/record_instance_constant_empty.dart.json.expect
@@ -0,0 +1,40 @@
+{
+  "metadata": {
+    "comment": "Recorded usages of objects tagged with a `RecordUse` annotation",
+    "version": "0.2.0"
+  },
+  "constants": [
+    {
+      "type": "Instance"
+    },
+    {
+      "type": "Instance",
+      "value": {
+        "a": 0
+      }
+    }
+  ],
+  "locations": [
+    {
+      "uri": "record_instance_constant_empty.dart"
+    }
+  ],
+  "recordings": [
+    {
+      "definition": {
+        "identifier": {
+          "uri": "record_instance_constant_empty.dart",
+          "name": "MyClass"
+        },
+        "loading_unit": "1"
+      },
+      "instances": [
+        {
+          "constant_index": 1,
+          "loading_unit": "1",
+          "@": 0
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file