[dart2js] Use new method encoding in runtime type analysis.

Change-Id: I41e6b6314266203e37badcb084becd95894bdf6c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/352966
Commit-Queue: Mayank Patke <fishythefish@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/compiler/lib/src/ir/impact_data.dart b/pkg/compiler/lib/src/ir/impact_data.dart
index 2416052..6623eed 100644
--- a/pkg/compiler/lib/src/ir/impact_data.dart
+++ b/pkg/compiler/lib/src/ir/impact_data.dart
@@ -170,14 +170,7 @@
 
   @override
   void visitStaticGet(ir.StaticGet node) {
-    final target = node.target;
-    if (target is ir.Procedure && target.kind == ir.ProcedureKind.Method) {
-      // TODO(johnniwinther): Remove this when dart2js uses the new method
-      // invocation encoding.
-      registerStaticTearOff(target, getDeferredImport(node));
-    } else {
-      registerStaticGet(node.target, getDeferredImport(node));
-    }
+    registerStaticGet(node.target, getDeferredImport(node));
   }
 
   @override
@@ -602,10 +595,6 @@
   void visitDynamicGet(ir.DynamicGet node) {
     final receiverType = node.receiver.getStaticType(staticTypeContext);
     registerDynamicGet(receiverType, node.name);
-    if (node.name.text == Identifiers.runtimeType_) {
-      // This handles `runtimeType` access on `Never`.
-      handleRuntimeTypeGet(receiverType, node);
-    }
     node.receiver.accept(this);
   }
 
@@ -708,11 +697,9 @@
     node.arguments.accept(this);
   }
 
-  // TODO(johnniwinther): Change the key to `InstanceGet` when the old method
-  //  invocation encoding is no longer used.
-  final Map<ir.Expression, RuntimeTypeUseData> _pendingRuntimeTypeUseData = {};
+  final Map<ir.InstanceGet, RuntimeTypeUseData> _pendingRuntimeTypeUseData = {};
 
-  void handleRuntimeTypeGet(ir.DartType receiverType, ir.Expression node) {
+  void handleRuntimeTypeGet(ir.DartType receiverType, ir.InstanceGet node) {
     RuntimeTypeUseData data =
         computeRuntimeTypeUse(_pendingRuntimeTypeUseData, node);
     if (data.leftRuntimeTypeExpression == node) {
diff --git a/pkg/compiler/lib/src/ir/runtime_type_analysis.dart b/pkg/compiler/lib/src/ir/runtime_type_analysis.dart
index b6ba6c1..1660dd3 100644
--- a/pkg/compiler/lib/src/ir/runtime_type_analysis.dart
+++ b/pkg/compiler/lib/src/ir/runtime_type_analysis.dart
@@ -5,7 +5,6 @@
 import 'package:kernel/ast.dart' as ir;
 
 import '../common/names.dart';
-import 'util.dart';
 
 /// Enum for recognized use kinds of `Object.runtimeType`.
 enum RuntimeTypeUseKind {
@@ -29,9 +28,7 @@
   final RuntimeTypeUseKind kind;
 
   /// The property get for the left (or single) occurrence of `.runtimeType`.
-  // TODO(johnniwinther): Change this to `InstanceGet` when the old method
-  // invocation encoding is no longer used.
-  final ir.Expression leftRuntimeTypeExpression;
+  final ir.InstanceGet leftRuntimeTypeExpression;
 
   /// The receiver expression.
   final ir.Expression receiver;
@@ -42,9 +39,7 @@
 
   /// The property get for the right occurrence of `.runtimeType` when [kind]
   /// is `RuntimeTypeUseKind.equals`.
-  // TODO(johnniwinther): Change this to `InstanceGet` when the old method
-  // invocation encoding is no longer used.
-  final ir.Expression? rightRuntimeTypeExpression;
+  final ir.InstanceGet? rightRuntimeTypeExpression;
 
   /// The argument expression if [kind] is `RuntimeTypeUseKind.equals`.
   final ir.Expression? argument;
@@ -80,58 +75,41 @@
 ///
 /// [cache] is used to ensure that only one [RuntimeTypeUseData] object is
 /// created per case, even for the `==` case.
-// TODO(johnniwinther): Change [cache] key and [node] to `InstanceGet` when the
-// old method invocation encoding is no longer used.
 RuntimeTypeUseData computeRuntimeTypeUse(
-    Map<ir.Expression, RuntimeTypeUseData> cache, ir.Expression node) {
+    Map<ir.InstanceGet, RuntimeTypeUseData> cache, ir.InstanceGet node) {
   RuntimeTypeUseData? receiverData = cache[node];
   if (receiverData != null) return receiverData;
 
-  /// Returns `true` if [node] is of the form `e.runtimeType`.
-  bool isGetRuntimeType(ir.TreeNode node) {
-    return node is ir.InstanceGet &&
-            node.name.text == Identifiers.runtimeType_ ||
-        node is ir.DynamicGet && node.name.text == Identifiers.runtimeType_;
-  }
+  assert(_isGetRuntimeType(node));
 
-  /// Returns `true` if [node] is of the form `e.toString()`.
-  bool isInvokeToString(ir.TreeNode? node) {
-    return node is ir.InstanceInvocation && node.name.text == 'toString';
-  }
-
-  assert(isGetRuntimeType(node));
-  // TODO(johnniwinther): Replace this with `node.receiver` when the old method
-  // invocation encoding is no longer used.
-  _RuntimeTypeAccess runtimeTypeAccess = _getRuntimeTypeAccess(node)!;
-
-  // TODO(johnniwinther): Change [receiverGet] and [argumentGet] to
-  // `InstanceGet` when the old method invocation encoding is no longer used.
   // TODO(johnniwinther): Special-case `this.runtimeType`.
-  late final ir.Expression receiverGet;
+  late final ir.InstanceGet receiverGet;
   late final ir.Expression receiver;
-  ir.Expression? argumentGet;
+  ir.InstanceGet? argumentGet;
   ir.Expression? argument;
   RuntimeTypeUseKind? kind;
 
   final nodeParent = node.parent;
   final nodeParentParent = nodeParent?.parent;
-  if (runtimeTypeAccess.receiver is ir.VariableGet &&
+  if (node.receiver is ir.VariableGet &&
       nodeParent is ir.ConditionalExpression &&
       nodeParentParent is ir.Let) {
-    NullAwareExpression? nullAware = getNullAwareExpression(nodeParentParent);
+    _NullAwareExpression? nullAware = getNullAwareExpression(nodeParentParent);
     if (nullAware != null) {
       // The node is of the form:
       //
       //     let #t1 = e in #t1 == null ? null : #t1.runtimeType
       //                                             ^
 
-      if (nullAware.parent is ir.VariableDeclaration &&
-          nullAware.parent.parent is ir.Let) {
-        NullAwareExpression? outer =
-            getNullAwareExpression(nullAware.parent.parent!);
+      final nullAwareParent = nullAware.parent;
+      final nullAwareParentParent = nullAwareParent.parent;
+      if (nullAwareParent is ir.VariableDeclaration &&
+          nullAwareParentParent is ir.Let) {
+        _NullAwareExpression? outer =
+            getNullAwareExpression(nullAwareParentParent);
         if (outer != null &&
             outer.receiver == nullAware.let &&
-            isInvokeToString(outer.expression)) {
+            _isInvokeToString(outer.expression)) {
           // Detected
           //
           //     e?.runtimeType?.toString()
@@ -146,17 +124,14 @@
           receiver = nullAware.receiver;
           receiverGet = node;
         }
-      } else if (_isObjectMethodInvocation(nullAware.parent)) {
-        _EqualsInvocation? equalsInvocation =
-            _getEqualsInvocation(nullAware.parent);
-        if (equalsInvocation != null &&
-            equalsInvocation.left == nullAware.let) {
+      } else if (_isObjectMethodInvocation(nullAwareParent)) {
+        if (nullAwareParent is ir.EqualsCall &&
+            nullAwareParent.left == nullAware.let) {
           // Detected
           //
           //  e0?.runtimeType == other
-          _RuntimeTypeAccess? otherGetRuntimeType =
-              _getRuntimeTypeAccess(equalsInvocation.right);
-          if (otherGetRuntimeType != null) {
+          if (_extractGetRuntimeType(nullAwareParent.right)
+              case final getRuntimeType?) {
             // Detected
             //
             //     e0?.runtimeType == e1.runtimeType
@@ -169,31 +144,33 @@
             kind = RuntimeTypeUseKind.equals;
             receiver = nullAware.receiver;
             receiverGet = node;
-            argument = otherGetRuntimeType.receiver;
-            argumentGet = otherGetRuntimeType.node;
+            argument = getRuntimeType.receiver;
+            argumentGet = getRuntimeType;
           }
 
-          NullAwareExpression? otherNullAware =
-              getNullAwareExpression(equalsInvocation.right);
-          if (otherNullAware != null &&
-              isGetRuntimeType(otherNullAware.expression)) {
-            // Detected
-            //
-            //     e0?.runtimeType == e1?.runtimeType
-            //         ^
-            // encoded as
-            //
-            //     (let #t1 = e0 in #t1 == null ? null : #t1.runtimeType)
-            //                                               ^
-            //         .==(let #t2 = e1 in #t2 == null ? null : #t2.runtimeType)
-            //
-            kind = RuntimeTypeUseKind.equals;
-            receiver = nullAware.receiver;
-            receiverGet = node;
-            argument = otherNullAware.receiver;
-            argumentGet = otherNullAware.expression;
+          _NullAwareExpression? otherNullAware =
+              getNullAwareExpression(nullAwareParent.right);
+          if (otherNullAware != null) {
+            if (_extractGetRuntimeType(otherNullAware.expression)
+                case final getRuntimeType?) {
+              // Detected
+              //
+              //     e0?.runtimeType == e1?.runtimeType
+              //         ^
+              // encoded as
+              //
+              //     (let #t1 = e0 in #t1 == null ? null : #t1.runtimeType)
+              //                                               ^
+              //         .==(let #t2 = e1 in #t2 == null ? null : #t2.runtimeType)
+              //
+              kind = RuntimeTypeUseKind.equals;
+              receiver = nullAware.receiver;
+              receiverGet = node;
+              argument = otherNullAware.receiver;
+              argumentGet = getRuntimeType;
+            }
           }
-        } else if (isInvokeToString(nullAware.parent)) {
+        } else if (_isInvokeToString(nullAwareParent)) {
           // Detected
           //
           //     e?.runtimeType.toString()
@@ -208,20 +185,17 @@
           receiver = nullAware.receiver;
           receiverGet = node;
         }
-      } else if (nullAware.parent is ir.Arguments &&
-          _isObjectMethodInvocation(nullAware.parent.parent)) {
-        _EqualsInvocation? equalsInvocation =
-            _getEqualsInvocation(nullAware.parent.parent);
-        if (equalsInvocation != null &&
-            equalsInvocation.right == nullAware.let) {
+      } else if (nullAwareParent is ir.Arguments &&
+          _isObjectMethodInvocation(nullAwareParentParent)) {
+        if (nullAwareParentParent is ir.EqualsCall &&
+            nullAwareParentParent.right == nullAware.let) {
           // [nullAware] is the right hand side of ==.
 
-          _RuntimeTypeAccess? otherGetRuntimeType =
-              _getRuntimeTypeAccess(equalsInvocation.left);
-          NullAwareExpression? otherNullAware =
-              getNullAwareExpression(equalsInvocation.left);
+          _NullAwareExpression? otherNullAware =
+              getNullAwareExpression(nullAwareParentParent.left);
 
-          if (otherGetRuntimeType != null) {
+          if (_extractGetRuntimeType(nullAwareParentParent.left)
+              case final getRuntimeType?) {
             // Detected
             //
             //     e0.runtimeType == e1?.runtimeType
@@ -232,31 +206,33 @@
             //         let #t1 = e1 in #t1 == null ? null : #t1.runtimeType)
             //                                                  ^
             kind = RuntimeTypeUseKind.equals;
-            receiver = otherGetRuntimeType.receiver;
-            receiverGet = otherGetRuntimeType.node;
+            receiver = getRuntimeType.receiver;
+            receiverGet = getRuntimeType;
             argument = nullAware.receiver;
             argumentGet = node;
           }
 
-          if (otherNullAware != null &&
-              isGetRuntimeType(otherNullAware.expression)) {
-            // Detected
-            //
-            //     e0?.runtimeType == e1?.runtimeType
-            //                            ^
-            // encoded as
-            //
-            //     (let #t1 = e0 in #t1 == null ? null : #t1.runtimeType)
-            //         .==(let #t2 = e1 in #t2 == null ? null : #t2.runtimeType)
-            //                                                      ^
-            kind = RuntimeTypeUseKind.equals;
-            receiver = otherNullAware.receiver;
-            receiverGet = otherNullAware.expression;
-            argument = nullAware.receiver;
-            argumentGet = node;
+          if (otherNullAware != null) {
+            if (_extractGetRuntimeType(otherNullAware.expression)
+                case final getRuntimeType?) {
+              // Detected
+              //
+              //     e0?.runtimeType == e1?.runtimeType
+              //                            ^
+              // encoded as
+              //
+              //     (let #t1 = e0 in #t1 == null ? null : #t1.runtimeType)
+              //         .==(let #t2 = e1 in #t2 == null ? null : #t2.runtimeType)
+              //                                                      ^
+              kind = RuntimeTypeUseKind.equals;
+              receiver = otherNullAware.receiver;
+              receiverGet = getRuntimeType;
+              argument = nullAware.receiver;
+              argumentGet = node;
+            }
           }
         }
-      } else if (nullAware.parent is ir.StringConcatenation) {
+      } else if (nullAwareParent is ir.StringConcatenation) {
         // Detected
         //
         //     '${e?.runtimeType}'
@@ -284,8 +260,8 @@
     }
   } else if (nodeParent is ir.VariableDeclaration &&
       nodeParentParent is ir.Let) {
-    NullAwareExpression? nullAware = getNullAwareExpression(nodeParentParent);
-    if (nullAware != null && isInvokeToString(nullAware.expression)) {
+    _NullAwareExpression? nullAware = getNullAwareExpression(nodeParentParent);
+    if (nullAware != null && _isInvokeToString(nullAware.expression)) {
       // Detected
       //
       //     e.runtimeType?.toString()
@@ -295,19 +271,16 @@
       //     let #t1 = e.runtimeType in #t1 == null ? null : #t1.toString()
       //                 ^
       kind = RuntimeTypeUseKind.string;
-      receiver = runtimeTypeAccess.receiver;
+      receiver = node.receiver;
       receiverGet = node;
     }
   } else if (_isObjectMethodInvocation(nodeParent)) {
-    _EqualsInvocation? equalsInvocation = _getEqualsInvocation(nodeParent);
-    if (equalsInvocation != null && equalsInvocation.left == node) {
+    if (nodeParent is ir.EqualsCall && nodeParent.left == node) {
       // [node] is the left hand side of ==.
 
-      _RuntimeTypeAccess? otherGetRuntimeType =
-          _getRuntimeTypeAccess(equalsInvocation.right);
-      NullAwareExpression? nullAware =
-          getNullAwareExpression(equalsInvocation.right);
-      if (otherGetRuntimeType != null) {
+      _NullAwareExpression? nullAware =
+          getNullAwareExpression(nodeParent.right);
+      if (_extractGetRuntimeType(nodeParent.right) case final getRuntimeType?) {
         // Detected
         //
         //     e0.runtimeType == e1.runtimeType
@@ -317,47 +290,47 @@
         //     e0.runtimeType.==(e1.runtimeType)
         //        ^
         kind = RuntimeTypeUseKind.equals;
-        receiver = runtimeTypeAccess.receiver;
+        receiver = node.receiver;
         receiverGet = node;
-        argument = otherGetRuntimeType.receiver;
-        argumentGet = otherGetRuntimeType.node;
-      } else if (nullAware != null && isGetRuntimeType(nullAware.expression)) {
-        // Detected
-        //
-        //     e0.runtimeType == e1?.runtimeType
-        //        ^
-        // encoded as
-        //
-        //     e0.runtimeType.==(
-        //        ^
-        //         let #t1 = e1 in #t1 == null ? null : #t1.runtimeType)
-        kind = RuntimeTypeUseKind.equals;
-        receiver = runtimeTypeAccess.receiver;
-        receiverGet = node;
-        argument = nullAware.receiver;
-        argumentGet = nullAware.expression;
+        argument = getRuntimeType.receiver;
+        argumentGet = getRuntimeType;
+      } else if (nullAware != null) {
+        if (_extractGetRuntimeType(nullAware.expression)
+            case final getRuntimeType?) {
+          // Detected
+          //
+          //     e0.runtimeType == e1?.runtimeType
+          //        ^
+          // encoded as
+          //
+          //     e0.runtimeType.==(
+          //        ^
+          //         let #t1 = e1 in #t1 == null ? null : #t1.runtimeType)
+          kind = RuntimeTypeUseKind.equals;
+          receiver = node.receiver;
+          receiverGet = node;
+          argument = nullAware.receiver;
+          argumentGet = getRuntimeType;
+        }
       }
-    } else if (isInvokeToString(nodeParent)) {
+    } else if (_isInvokeToString(nodeParent)) {
       // Detected
       //
       //     e.runtimeType.toString()
       //       ^
       kind = RuntimeTypeUseKind.string;
-      receiver = runtimeTypeAccess.receiver;
+      receiver = node.receiver;
       receiverGet = node;
     }
   } else if (nodeParent is ir.Arguments &&
       _isObjectMethodInvocation(nodeParentParent)) {
-    _EqualsInvocation? _equalsInvocation =
-        _getEqualsInvocation(nodeParentParent);
-    if (_equalsInvocation != null && _equalsInvocation.right == node) {
+    if (nodeParentParent is ir.EqualsCall && nodeParentParent.right == node) {
       // [node] is the right hand side of ==.
-      _RuntimeTypeAccess? otherGetRuntimeType =
-          _getRuntimeTypeAccess(_equalsInvocation.left);
-      NullAwareExpression? nullAware =
-          getNullAwareExpression(_equalsInvocation.left);
+      _NullAwareExpression? nullAware =
+          getNullAwareExpression(nodeParentParent.left);
 
-      if (otherGetRuntimeType != null) {
+      if (_extractGetRuntimeType(nodeParentParent.left)
+          case final getRuntimeType?) {
         // Detected
         //
         //     e0.runtimeType == e1.runtimeType
@@ -367,25 +340,28 @@
         //     e0.runtimeType.==(e1.runtimeType)
         //                          ^
         kind = RuntimeTypeUseKind.equals;
-        receiver = otherGetRuntimeType.receiver;
-        receiverGet = otherGetRuntimeType.node;
-        argument = runtimeTypeAccess.receiver;
+        receiver = getRuntimeType.receiver;
+        receiverGet = getRuntimeType;
+        argument = node.receiver;
         argumentGet = node;
-      } else if (nullAware != null && isGetRuntimeType(nullAware.expression)) {
-        // Detected
-        //
-        //     e0?.runtimeType == e1.runtimeType
-        //                           ^
-        // encoded as
-        //
-        //     (let #t1 = e0 in #t1 == null ? null : #t1.runtimeType)
-        //         .==(e1.runtimeType)
-        //                ^
-        kind = RuntimeTypeUseKind.equals;
-        receiver = nullAware.receiver;
-        receiverGet = nullAware.expression;
-        argument = runtimeTypeAccess.receiver;
-        argumentGet = node;
+      } else if (nullAware != null) {
+        if (_extractGetRuntimeType(nullAware.expression)
+            case final getRuntimeType?) {
+          // Detected
+          //
+          //     e0?.runtimeType == e1.runtimeType
+          //                           ^
+          // encoded as
+          //
+          //     (let #t1 = e0 in #t1 == null ? null : #t1.runtimeType)
+          //         .==(e1.runtimeType)
+          //                ^
+          kind = RuntimeTypeUseKind.equals;
+          receiver = nullAware.receiver;
+          receiverGet = getRuntimeType;
+          argument = node.receiver;
+          argumentGet = node;
+        }
       }
     }
   } else if (nodeParent is ir.StringConcatenation) {
@@ -394,7 +370,7 @@
     //     '${e.runtimeType}'
     //          ^
     kind = RuntimeTypeUseKind.string;
-    receiver = runtimeTypeAccess.receiver;
+    receiver = node.receiver;
     receiverGet = node;
   }
 
@@ -404,7 +380,7 @@
     //     e.runtimeType
     //       ^
     kind = RuntimeTypeUseKind.unknown;
-    receiver = runtimeTypeAccess.receiver;
+    receiver = node.receiver;
     receiverGet = node;
   }
 
@@ -427,37 +403,60 @@
   return node is ir.InstanceInvocation || node is ir.EqualsCall;
 }
 
-/// Returns the [_RuntimeTypeAccess] corresponding to [node] if it is an access
-/// of `.runtimeType`, and `null` otherwise.
-_RuntimeTypeAccess? _getRuntimeTypeAccess(ir.TreeNode node) {
-  if (node is ir.InstanceGet && node.name.text == 'runtimeType') {
-    return _RuntimeTypeAccess(node, node.receiver);
-  } else if (node is ir.DynamicGet && node.name.text == 'runtimeType') {
-    return _RuntimeTypeAccess(node, node.receiver);
+/// Returns `true` if [node] is of the form `e.runtimeType` and `null`
+/// otherwise.
+bool _isGetRuntimeType(ir.InstanceGet node) =>
+    node.name.text == Identifiers.runtimeType_;
+
+/// Returns `true` if [node] is of the form `e.toString()`.
+bool _isInvokeToString(ir.TreeNode? node) =>
+    node is ir.InstanceInvocation && node.name.text == 'toString';
+
+ir.InstanceGet? _extractGetRuntimeType(ir.TreeNode node) =>
+    node is ir.InstanceGet && _isGetRuntimeType(node) ? node : null;
+
+/// Kernel encodes a null-aware expression `a?.b` as
+///
+///     let final #1 = a in #1 == null ? null : #1.b
+///
+/// [getNullAwareExpression] recognizes such expressions storing the result in
+/// a [_NullAwareExpression] object.
+///
+/// [syntheticVariable] holds the synthesized `#1` variable. [expression] holds
+/// the `#1.b` expression. [receiver] returns `a` expression. [parent] returns
+/// the parent of the let node, i.e. the parent node of the original null-aware
+/// expression. [let] returns the let node created for the encoding.
+class _NullAwareExpression {
+  final ir.Let let;
+  final ir.VariableDeclaration syntheticVariable;
+  final ir.Expression expression;
+
+  _NullAwareExpression(this.let, this.syntheticVariable, this.expression);
+
+  ir.Expression get receiver => syntheticVariable.initializer!;
+
+  ir.TreeNode get parent => let.parent!;
+
+  @override
+  String toString() => let.toString();
+}
+
+_NullAwareExpression? getNullAwareExpression(ir.TreeNode node) {
+  if (node is ir.Let) {
+    ir.Expression body = node.body;
+    if (node.variable.name == null &&
+        node.variable.isFinal &&
+        body is ir.ConditionalExpression) {
+      final condition = body.condition;
+      if (condition is ir.EqualsNull) {
+        ir.Expression receiver = condition.expression;
+        if (receiver is ir.VariableGet && receiver.variable == node.variable) {
+          // We have
+          //   let #t1 = e0 in #t1 == null ? null : e1
+          return _NullAwareExpression(node, node.variable, body.otherwise);
+        }
+      }
+    }
   }
   return null;
 }
-
-class _RuntimeTypeAccess {
-  final ir.Expression node;
-  final ir.Expression receiver;
-
-  _RuntimeTypeAccess(this.node, this.receiver);
-}
-
-/// Returns the [_EqualsInvocation] corresponding to [node] if it is a call to
-/// of `==`, and `null` otherwise.
-_EqualsInvocation? _getEqualsInvocation(ir.TreeNode? node) {
-  if (node is ir.EqualsCall) {
-    return _EqualsInvocation(node, node.left, node.right);
-  }
-  return null;
-}
-
-class _EqualsInvocation {
-  final ir.Expression node;
-  final ir.Expression left;
-  final ir.Expression right;
-
-  _EqualsInvocation(this.node, this.left, this.right);
-}
diff --git a/pkg/compiler/lib/src/ir/util.dart b/pkg/compiler/lib/src/ir/util.dart
index 0a319e3..09c5983 100644
--- a/pkg/compiler/lib/src/ir/util.dart
+++ b/pkg/compiler/lib/src/ir/util.dart
@@ -101,52 +101,6 @@
       (node is ir.ConstantExpression && node.constant is ir.NullConstant);
 }
 
-/// Kernel encodes a null-aware expression `a?.b` as
-///
-///     let final #1 = a in #1 == null ? null : #1.b
-///
-/// [getNullAwareExpression] recognizes such expressions storing the result in
-/// a [NullAwareExpression] object.
-///
-/// [syntheticVariable] holds the synthesized `#1` variable. [expression] holds
-/// the `#1.b` expression. [receiver] returns `a` expression. [parent] returns
-/// the parent of the let node, i.e. the parent node of the original null-aware
-/// expression. [let] returns the let node created for the encoding.
-class NullAwareExpression {
-  final ir.Let let;
-  final ir.VariableDeclaration syntheticVariable;
-  final ir.Expression expression;
-
-  NullAwareExpression(this.let, this.syntheticVariable, this.expression);
-
-  ir.Expression get receiver => syntheticVariable.initializer!;
-
-  ir.TreeNode get parent => let.parent!;
-
-  @override
-  String toString() => let.toString();
-}
-
-NullAwareExpression? getNullAwareExpression(ir.TreeNode node) {
-  if (node is ir.Let) {
-    ir.Expression body = node.body;
-    if (node.variable.name == null &&
-        node.variable.isFinal &&
-        body is ir.ConditionalExpression) {
-      final condition = body.condition;
-      if (condition is ir.EqualsNull) {
-        ir.Expression receiver = condition.expression;
-        if (receiver is ir.VariableGet && receiver.variable == node.variable) {
-          // We have
-          //   let #t1 = e0 in #t1 == null ? null : e1
-          return NullAwareExpression(node, node.variable, body.otherwise);
-        }
-      }
-    }
-  }
-  return null;
-}
-
 /// Check whether [node] is immediately guarded by a
 /// [ir.CheckLibraryIsLoaded], and hence the node is a deferred access.
 ir.LibraryDependency? getDeferredImport(ir.TreeNode node) {