[dart2wasm] Consolidate workarounds for type mismatches

In a various places in the compiler, we have had situations with
type mismatches that prompted workarounds.
These fell into a number of cases:

- Calls of functions with return type `Null` or `Never`. These were
  treated like `void` and given no output types. This is now fixed, so
  `Null` or `Never` in return types are just translated to their Wasm
  representations, currently `(ref null #Top)`, later (when we add the
  Wasm bottom types) `(ref null none)`.
- No-value `return` statements in `sync*` functions. Since we don't
  support `sync*`, it's fine that these are broken.
- Constants passed directly to imported functions, or returned from
  exported functions, such that the expected type is `externref`.
  A special case is added for this.
- Constants produced by the TFA constant propagation in places where
  they are incompatible with the context type (e.g. `null` in non-
  nullable context). This is now handled generally in constant
  instantiation and assumed to be always unreachable.

Change-Id: I43915db1b0c91ce0933a16bf36d6108a9cf390f4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/268001
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Aske Simon Christensen <askesc@google.com>
diff --git a/pkg/dart2wasm/lib/constants.dart b/pkg/dart2wasm/lib/constants.dart
index 0526532..9f6f75a2 100644
--- a/pkg/dart2wasm/lib/constants.dart
+++ b/pkg/dart2wasm/lib/constants.dart
@@ -235,8 +235,17 @@
 
   void instantiate(Constant constant) {
     w.ValueType resultType = constant.accept(this);
-    assert(!translator.needsConversion(resultType, expectedType),
-        "For $constant: expected $expectedType, got $resultType");
+    if (translator.needsConversion(resultType, expectedType)) {
+      if (expectedType == const w.RefType.extern(nullable: true)) {
+        assert(resultType.isSubtypeOf(w.RefType.any(nullable: true)));
+        b.extern_externalize();
+      } else {
+        // This only happens in invalid but unreachable code produced by the
+        // TFA dead-code elimination.
+        b.comment("Constant in incompatible context");
+        b.unreachable();
+      }
+    }
   }
 
   @override
@@ -273,22 +282,11 @@
 
   @override
   w.ValueType visitNullConstant(NullConstant node) {
-    w.ValueType? expectedType = this.expectedType;
-    if (expectedType != translator.voidMarker) {
-      if (expectedType.nullable) {
-        w.HeapType heapType =
-            expectedType is w.RefType ? expectedType.heapType : w.HeapType.data;
-        b.ref_null(heapType);
-      } else {
-        // This only happens in invalid but unreachable code produced by the
-        // TFA dead-code elimination.
-        b.comment("Non-nullable null constant");
-        b.block(const [], [expectedType]);
-        b.unreachable();
-        b.end();
-      }
-    }
-    return expectedType;
+    w.ValueType expectedType = this.expectedType;
+    w.HeapType heapType =
+        expectedType is w.RefType ? expectedType.heapType : w.HeapType.data;
+    b.ref_null(heapType);
+    return w.RefType(heapType, nullable: true);
   }
 
   w.ValueType _maybeBox(w.ValueType wasmType, void Function() pushValue) {
diff --git a/pkg/dart2wasm/lib/functions.dart b/pkg/dart2wasm/lib/functions.dart
index 7e73b87..a72829d 100644
--- a/pkg/dart2wasm/lib/functions.dart
+++ b/pkg/dart2wasm/lib/functions.dart
@@ -239,9 +239,7 @@
     inputs.addAll(
         params.map((t) => adjustExternalType(translator.translateType(t))));
 
-    List<w.ValueType> outputs = returnType is VoidType ||
-            returnType is NeverType ||
-            returnType is NullType
+    List<w.ValueType> outputs = returnType is VoidType
         ? member.function?.asyncMarker == AsyncMarker.Async
             ? [adjustExternalType(translator.topInfo.nullableType)]
             : const []
diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart
index 648e772..358796f 100644
--- a/pkg/dart2wasm/lib/translator.dart
+++ b/pkg/dart2wasm/lib/translator.dart
@@ -851,17 +851,11 @@
         return;
       }
       if (to != voidMarker) {
-        if (to is w.RefType && to.nullable) {
-          // This can happen when a void method has its return type overridden
-          // to return a value, in which case the selector signature will have a
-          // non-void return type to encompass all possible return values.
-          b.ref_null(to.heapType);
-        } else {
-          // This only happens in invalid but unreachable code produced by the
-          // TFA dead-code elimination.
-          b.comment("Non-nullable void conversion");
-          b.unreachable();
-        }
+        assert(to is w.RefType && to.nullable);
+        // This can happen when a void method has its return type overridden
+        // to return a value, in which case the selector signature will have a
+        // non-void return type to encompass all possible return values.
+        b.ref_null((to as w.RefType).heapType);
         return;
       }
     }