[ddc] Move FutureOr normalization to its own visitor

Will make it easier to reuse the normalization when compiling
with the new runtime type representation.

Change-Id: Ie767a2b676950205b0b50eadac305c29914433f2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/247420
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Nicholas Shahan <nshahan@google.com>
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index 04deb4c..b249140 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -32,6 +32,7 @@
 import '../js_ast/source_map_printer.dart'
     show NodeEnd, NodeSpan, HoverComment, continueSourceMap;
 import 'constants.dart';
+import 'future_or_normalizer.dart';
 import 'js_interop.dart';
 import 'js_typerep.dart';
 import 'kernel_helpers.dart';
@@ -939,7 +940,7 @@
           }
           return _emitInterfaceType(t, emitNullability: emitNullability);
         } else if (t is FutureOrType) {
-          var normalizedType = _normalizeFutureOr(t);
+          var normalizedType = normalizeFutureOr(t, _coreTypes);
           if (normalizedType is FutureOrType) {
             _declareBeforeUse(_coreTypes.deprecatedFutureOrClass);
             var typeRep = _emitFutureOrTypeWithArgument(
@@ -2911,64 +2912,6 @@
           ? visitNullType(const NullType())
           : _emitNullabilityWrapper(runtimeCall('Never'), type.nullability);
 
-  /// Normalizes `FutureOr` types.
-  ///
-  /// Any changes to the normalization logic here should be mirrored in the
-  /// classes.dart runtime library method named `normalizeFutureOr`.
-  DartType _normalizeFutureOr(FutureOrType futureOr) {
-    var typeArgument = futureOr.typeArgument;
-    if (typeArgument is DynamicType) {
-      // FutureOr<dynamic> --> dynamic
-      return typeArgument;
-    }
-    if (typeArgument is VoidType) {
-      // FutureOr<void> --> void
-      return typeArgument;
-    }
-
-    if (typeArgument is InterfaceType &&
-        typeArgument.classNode == _coreTypes.objectClass) {
-      // Normalize FutureOr of Object, Object?, Object*.
-      var nullable = futureOr.nullability == Nullability.nullable ||
-          typeArgument.nullability == Nullability.nullable;
-      var legacy = futureOr.nullability == Nullability.legacy ||
-          typeArgument.nullability == Nullability.legacy;
-      var nullability = nullable
-          ? Nullability.nullable
-          : legacy
-              ? Nullability.legacy
-              : Nullability.nonNullable;
-      return typeArgument.withDeclaredNullability(nullability);
-    } else if (typeArgument is NeverType) {
-      // FutureOr<Never> --> Future<Never>
-      return InterfaceType(
-          _coreTypes.futureClass, futureOr.nullability, [typeArgument]);
-    } else if (typeArgument is NullType) {
-      // FutureOr<Null> --> Future<Null>?
-      return InterfaceType(
-          _coreTypes.futureClass, Nullability.nullable, [typeArgument]);
-    } else if (futureOr.declaredNullability == Nullability.nullable &&
-        typeArgument.nullability == Nullability.nullable) {
-      // FutureOr<T?>? --> FutureOr<T?>
-      return futureOr.withDeclaredNullability(Nullability.nonNullable);
-    }
-    // The following is not part of the normalization spec but this is a
-    // convenient place to perform this change of nullability consistently. This
-    // only applies at compile-time and is not needed in the runtime version of
-    // the FutureOr normalization.
-    // FutureOr<T%>% --> FutureOr<T%>
-    //
-    // If the type argument has undetermined nullability the CFE propagates
-    // it to the FutureOr type as well. In this case we can represent the
-    // FutureOr type without any nullability wrappers and rely on the runtime to
-    // handle the nullability of the instantiated type appropriately.
-    if (futureOr.nullability == Nullability.undetermined &&
-        typeArgument.nullability == Nullability.undetermined) {
-      return futureOr.withDeclaredNullability(Nullability.nonNullable);
-    }
-    return futureOr;
-  }
-
   @override
   js_ast.Expression visitInterfaceType(InterfaceType type) =>
       _emitInterfaceType(type);
@@ -2979,7 +2922,7 @@
 
   @override
   js_ast.Expression visitFutureOrType(FutureOrType type) {
-    var normalizedType = _normalizeFutureOr(type);
+    var normalizedType = normalizeFutureOr(type, _coreTypes);
     return normalizedType is FutureOrType
         ? _emitFutureOrType(normalizedType)
         : normalizedType.accept(this);
diff --git a/pkg/dev_compiler/lib/src/kernel/future_or_normalizer.dart b/pkg/dev_compiler/lib/src/kernel/future_or_normalizer.dart
new file mode 100644
index 0000000..492167b
--- /dev/null
+++ b/pkg/dev_compiler/lib/src/kernel/future_or_normalizer.dart
@@ -0,0 +1,91 @@
+// Copyright (c) 2022, 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:kernel/ast.dart';
+import 'package:kernel/core_types.dart';
+import 'package:kernel/kernel.dart';
+import 'package:kernel/src/replacement_visitor.dart';
+
+/// Normalizes all `FutureOr` types found in [type].
+DartType normalizeFutureOr(DartType type, CoreTypes coreTypes) =>
+    type.accept1(_FutureOrNormalizer.instance(coreTypes), Variance.unrelated) ??
+    type;
+
+/// Visit methods returns a normalized version of `FutureOr` types or `null` if
+/// no normalization was applied.
+///
+/// The `variance` parameters in the visit methods are unused in this type
+/// replacement.
+///
+/// `FutureOr` types are normalized per the spec:
+/// https://github.com/dart-lang/language/blob/master/resources/type-system/normalization.md
+///
+/// Any changes to the normalization logic here should be mirrored in the
+/// classes.dart runtime library method named `normalizeFutureOr`.
+class _FutureOrNormalizer extends ReplacementVisitor {
+  final CoreTypes _coreTypes;
+
+  static _FutureOrNormalizer? _instance;
+
+  _FutureOrNormalizer._(this._coreTypes);
+
+  factory _FutureOrNormalizer.instance(CoreTypes coreTypes) =>
+      _instance ?? (_instance = _FutureOrNormalizer._(coreTypes));
+
+  @override
+  DartType? visitFutureOrType(FutureOrType futureOr, int variance) {
+    var normalizedTypeArg = futureOr.typeArgument.accept1(this, variance);
+    var typeArgument = normalizedTypeArg ?? futureOr.typeArgument;
+    if (typeArgument is DynamicType) {
+      // FutureOr<dynamic> --> dynamic
+      return typeArgument;
+    }
+    if (typeArgument is VoidType) {
+      // FutureOr<void> --> void
+      return typeArgument;
+    }
+
+    if (typeArgument is InterfaceType &&
+        typeArgument.classNode == _coreTypes.objectClass) {
+      // Normalize FutureOr of Object, Object?, Object*.
+      var nullable = futureOr.nullability == Nullability.nullable ||
+          typeArgument.nullability == Nullability.nullable;
+      var legacy = futureOr.nullability == Nullability.legacy ||
+          typeArgument.nullability == Nullability.legacy;
+      var nullability = nullable
+          ? Nullability.nullable
+          : legacy
+              ? Nullability.legacy
+              : Nullability.nonNullable;
+      return typeArgument.withDeclaredNullability(nullability);
+    } else if (typeArgument is NeverType) {
+      // FutureOr<Never> --> Future<Never>
+      return InterfaceType(
+          _coreTypes.futureClass, futureOr.nullability, [typeArgument]);
+    } else if (typeArgument is NullType) {
+      // FutureOr<Null> --> Future<Null>?
+      return InterfaceType(
+          _coreTypes.futureClass, Nullability.nullable, [typeArgument]);
+    } else if (futureOr.declaredNullability == Nullability.nullable &&
+        typeArgument.nullability == Nullability.nullable) {
+      // FutureOr<T?>? --> FutureOr<T?>
+      return futureOr.withDeclaredNullability(Nullability.nonNullable);
+    }
+    // The following is not part of the normalization spec but this is a
+    // convenient place to perform this change of nullability consistently. This
+    // only applies at compile-time and is not needed in the runtime version of
+    // the FutureOr normalization.
+    // FutureOr<T%>% --> FutureOr<T%>
+    //
+    // If the type argument has undetermined nullability the CFE propagates
+    // it to the FutureOr type as well. In this case we can represent the
+    // FutureOr type without any nullability wrappers and rely on the runtime to
+    // handle the nullability of the instantiated type appropriately.
+    if (futureOr.declaredNullability == Nullability.undetermined &&
+        typeArgument.declaredNullability == Nullability.undetermined) {
+      return futureOr.withDeclaredNullability(Nullability.nonNullable);
+    }
+    return null;
+  }
+}