Version 2.18.0-35.0.dev

Merge commit 'fec466e3fe726060d411bc0dc2ed5ae5977a2ab1' into 'dev'
diff --git a/DEPS b/DEPS
index 0416bb7..a2c215f 100644
--- a/DEPS
+++ b/DEPS
@@ -39,7 +39,7 @@
 
   # Checked-in SDK version. The checked-in SDK is a Dart SDK distribution in a
   # cipd package used to run Dart scripts in the build and test infrastructure.
-  "sdk_tag": "version:2.16.2",
+  "sdk_tag": "version:2.17.0-266.1.beta",
 
   # co19 is a cipd package. Use update.sh in tests/co19[_2] to update these
   # hashes. It requires access to the dart-build-access group, which EngProd
diff --git a/pkg/_fe_analyzer_shared/lib/src/scanner/abstract_scanner.dart b/pkg/_fe_analyzer_shared/lib/src/scanner/abstract_scanner.dart
index 8c6a343..1f81c6b 100644
--- a/pkg/_fe_analyzer_shared/lib/src/scanner/abstract_scanner.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/scanner/abstract_scanner.dart
@@ -9,7 +9,15 @@
 import 'dart:typed_data' show Uint16List, Uint32List;
 
 import 'token.dart'
-    show BeginToken, Keyword, KeywordToken, SyntheticToken, Token, TokenType;
+    show
+        BeginToken,
+        CommentToken,
+        Keyword,
+        KeywordToken,
+        LanguageVersionToken,
+        SyntheticToken,
+        Token,
+        TokenType;
 
 import 'token.dart' as analyzer show StringToken;
 
@@ -34,8 +42,7 @@
 
 import 'keyword_state.dart' show KeywordState;
 
-import 'token_impl.dart'
-    show CommentToken, DartDocToken, LanguageVersionToken, StringToken;
+import 'token_impl.dart' show DartDocToken, StringTokenImpl;
 
 import 'token_constants.dart';
 
@@ -570,11 +577,9 @@
       {
         AbstractScanner option1 = createRecoveryOptionScanner();
         option1.insertSyntheticClosers(originalStack, groupingStack);
-        option1Recoveries =
-            option1.recoveryOptionTokenizer(option1.appendEndGroupInternal(
-                /* foundMatchingBrace = */ true,
-                type,
-                openKind));
+        option1Recoveries = option1.recoveryOptionTokenizer(
+            option1.appendEndGroupInternal(
+                /* foundMatchingBrace = */ true, type, openKind));
         option1Recoveries += option1.groupingStack.slowLength();
       }
 
@@ -583,11 +588,9 @@
       {
         AbstractScanner option2 = createRecoveryOptionScanner();
         option2.groupingStack = originalStack;
-        option2Recoveries =
-            option2.recoveryOptionTokenizer(option2.appendEndGroupInternal(
-                /* foundMatchingBrace = */ false,
-                type,
-                openKind));
+        option2Recoveries = option2.recoveryOptionTokenizer(
+            option2.appendEndGroupInternal(
+                /* foundMatchingBrace = */ false, type, openKind));
         // We add 1 to make this option pay for ignoring this token.
         option2Recoveries += option2.groupingStack.slowLength() + 1;
       }
@@ -1914,7 +1917,7 @@
         codeUnits.add(next);
         next = advance();
       }
-      appendToken(new StringToken.fromString(
+      appendToken(new StringTokenImpl.fromString(
           TokenType.IDENTIFIER, new String.fromCharCodes(codeUnits), charOffset,
           precedingComments: comments));
       return next;
diff --git a/pkg/_fe_analyzer_shared/lib/src/scanner/recover.dart b/pkg/_fe_analyzer_shared/lib/src/scanner/recover.dart
index c18e6b1..616fa2b 100644
--- a/pkg/_fe_analyzer_shared/lib/src/scanner/recover.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/scanner/recover.dart
@@ -6,7 +6,7 @@
 
 import 'token.dart' show Token, TokenType;
 
-import 'token_impl.dart' show StringToken;
+import 'token_impl.dart' show StringTokenImpl;
 
 import 'error_token.dart' show ErrorToken;
 
@@ -49,7 +49,7 @@
 }
 
 Token synthesizeToken(int charOffset, String value, TokenType type) {
-  return new StringToken.fromString(type, value, charOffset);
+  return new StringTokenImpl.fromString(type, value, charOffset);
 }
 
 Token skipToEof(Token token) {
diff --git a/pkg/_fe_analyzer_shared/lib/src/scanner/scanner.dart b/pkg/_fe_analyzer_shared/lib/src/scanner/scanner.dart
index 35a6386..b9b7cbc 100644
--- a/pkg/_fe_analyzer_shared/lib/src/scanner/scanner.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/scanner/scanner.dart
@@ -22,8 +22,7 @@
 
 export 'token_impl.dart'
     show
-        LanguageVersionToken,
-        StringToken,
+        StringTokenImpl,
         isBinaryOperator,
         isMinusOperator,
         isTernaryOperator,
@@ -32,7 +31,7 @@
 
 export 'error_token.dart' show ErrorToken, buildUnexpectedCharacterToken;
 
-export 'token_impl.dart' show LanguageVersionToken;
+export 'token.dart' show LanguageVersionToken;
 
 export 'token_constants.dart' show EOF_TOKEN;
 
diff --git a/pkg/_fe_analyzer_shared/lib/src/scanner/string_scanner.dart b/pkg/_fe_analyzer_shared/lib/src/scanner/string_scanner.dart
index 7594362..f31fbb1 100644
--- a/pkg/_fe_analyzer_shared/lib/src/scanner/string_scanner.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/scanner/string_scanner.dart
@@ -4,7 +4,13 @@
 
 library dart2js.scanner.string_scanner;
 
-import 'token.dart' show Token, SyntheticStringToken, TokenType;
+import 'token.dart'
+    show
+        CommentToken,
+        LanguageVersionToken,
+        SyntheticStringToken,
+        Token,
+        TokenType;
 
 import 'token.dart' as analyzer show StringToken;
 
@@ -12,7 +18,11 @@
     show AbstractScanner, LanguageVersionChanged, ScannerConfiguration;
 
 import 'token_impl.dart'
-    show CommentToken, DartDocToken, LanguageVersionToken, StringToken;
+    show
+        CommentTokenImpl,
+        DartDocToken,
+        LanguageVersionTokenImpl,
+        StringTokenImpl;
 
 import 'error_token.dart' show ErrorToken;
 
@@ -69,7 +79,7 @@
   analyzer.StringToken createSubstringToken(
       TokenType type, int start, bool asciiOnly,
       [int extraOffset = 0]) {
-    return new StringToken.fromSubstring(
+    return new StringTokenImpl.fromSubstring(
         type, string, start, scanOffset + extraOffset, tokenStart,
         canonicalize: true, precedingComments: comments);
   }
@@ -85,7 +95,7 @@
   @override
   CommentToken createCommentToken(TokenType type, int start, bool asciiOnly,
       [int extraOffset = 0]) {
-    return new CommentToken.fromSubstring(
+    return new CommentTokenImpl.fromSubstring(
         type, string, start, scanOffset + extraOffset, tokenStart,
         canonicalize: true);
   }
@@ -101,7 +111,7 @@
   @override
   LanguageVersionToken createLanguageVersionToken(
       int start, int major, int minor) {
-    return new LanguageVersionToken.fromSubstring(
+    return new LanguageVersionTokenImpl.fromSubstring(
         string, start, scanOffset, tokenStart, major, minor,
         canonicalize: true);
   }
diff --git a/pkg/_fe_analyzer_shared/lib/src/scanner/token_impl.dart b/pkg/_fe_analyzer_shared/lib/src/scanner/token_impl.dart
index da39df5..986340a 100644
--- a/pkg/_fe_analyzer_shared/lib/src/scanner/token_impl.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/scanner/token_impl.dart
@@ -4,8 +4,14 @@
 
 library _fe_analyzer_shared.scanner.token;
 
-import 'token.dart' as analyzer;
-import 'token.dart' show TokenType;
+import 'token.dart'
+    show
+        DocumentationCommentToken,
+        SimpleToken,
+        TokenType,
+        CommentToken,
+        StringToken,
+        LanguageVersionToken;
 
 import 'token_constants.dart' show IDENTIFIER_TOKEN;
 
@@ -16,7 +22,7 @@
  * number literals, comments, and error tokens, using the corresponding
  * precedence info.
  */
-class StringToken extends analyzer.SimpleToken implements analyzer.StringToken {
+class StringTokenImpl extends SimpleToken implements StringToken {
   /**
    * The length threshold above which substring tokens are computed lazily.
    *
@@ -33,8 +39,8 @@
    * Creates a non-lazy string token. If [canonicalize] is true, the string
    * is canonicalized before the token is created.
    */
-  StringToken.fromString(TokenType type, String value, int charOffset,
-      {bool canonicalize: false, analyzer.CommentToken? precedingComments})
+  StringTokenImpl.fromString(TokenType type, String value, int charOffset,
+      {bool canonicalize: false, CommentToken? precedingComments})
       : valueOrLazySubstring = canonicalizedString(
             value, /* start = */ 0, value.length, canonicalize),
         super(type, charOffset, precedingComments);
@@ -43,9 +49,9 @@
    * Creates a lazy string token. If [canonicalize] is true, the string
    * is canonicalized before the token is created.
    */
-  StringToken.fromSubstring(
+  StringTokenImpl.fromSubstring(
       TokenType type, String data, int start, int end, int charOffset,
-      {bool canonicalize: false, analyzer.CommentToken? precedingComments})
+      {bool canonicalize: false, CommentToken? precedingComments})
       : super(type, charOffset, precedingComments) {
     int length = end - start;
     if (length <= LAZY_THRESHOLD) {
@@ -61,9 +67,9 @@
    * Creates a lazy string token. If [asciiOnly] is false, the byte array
    * is passed through a UTF-8 decoder.
    */
-  StringToken.fromUtf8Bytes(TokenType type, List<int> data, int start, int end,
-      bool asciiOnly, int charOffset,
-      {analyzer.CommentToken? precedingComments})
+  StringTokenImpl.fromUtf8Bytes(TokenType type, List<int> data, int start,
+      int end, bool asciiOnly, int charOffset,
+      {CommentToken? precedingComments})
       : super(type, charOffset, precedingComments) {
     int length = end - start;
     if (length <= LAZY_THRESHOLD) {
@@ -73,10 +79,6 @@
     }
   }
 
-  StringToken._(TokenType type, this.valueOrLazySubstring, int charOffset,
-      [analyzer.CommentToken? precedingComments])
-      : super(type, charOffset, precedingComments);
-
   @override
   String get lexeme {
     if (valueOrLazySubstring is String) {
@@ -119,28 +121,15 @@
   String value() => lexeme;
 }
 
-/**
- * A String-valued token that does not exist in the original source.
- */
-class SyntheticStringToken extends StringToken
-    implements analyzer.SyntheticStringToken {
-  SyntheticStringToken(TokenType type, String value, int offset,
-      [analyzer.CommentToken? precedingComments])
-      : super._(type, value, offset, precedingComments);
-
+class CommentTokenImpl extends StringTokenImpl implements CommentToken {
   @override
-  int get length => 0;
-}
-
-class CommentToken extends StringToken implements analyzer.CommentToken {
-  @override
-  analyzer.SimpleToken? parent;
+  SimpleToken? parent;
 
   /**
    * Creates a lazy comment token. If [canonicalize] is true, the string
    * is canonicalized before the token is created.
    */
-  CommentToken.fromSubstring(
+  CommentTokenImpl.fromSubstring(
       TokenType type, String data, int start, int end, int charOffset,
       {bool canonicalize: false})
       : super.fromSubstring(type, data, start, end, charOffset,
@@ -149,44 +138,44 @@
   /**
    * Creates a non-lazy comment token.
    */
-  CommentToken.fromString(TokenType type, String lexeme, int charOffset)
+  CommentTokenImpl.fromString(TokenType type, String lexeme, int charOffset)
       : super.fromString(type, lexeme, charOffset);
 
   /**
    * Creates a lazy string token. If [asciiOnly] is false, the byte array
    * is passed through a UTF-8 decoder.
    */
-  CommentToken.fromUtf8Bytes(TokenType type, List<int> data, int start, int end,
-      bool asciiOnly, int charOffset)
+  CommentTokenImpl.fromUtf8Bytes(TokenType type, List<int> data, int start,
+      int end, bool asciiOnly, int charOffset)
       : super.fromUtf8Bytes(type, data, start, end, asciiOnly, charOffset);
 }
 
-class LanguageVersionToken extends CommentToken
-    implements analyzer.LanguageVersionToken {
+class LanguageVersionTokenImpl extends CommentTokenImpl
+    implements LanguageVersionToken {
   @override
   int major;
 
   @override
   int minor;
 
-  LanguageVersionToken.from(String text, int offset, this.major, this.minor)
+  LanguageVersionTokenImpl.from(String text, int offset, this.major, this.minor)
       : super.fromString(TokenType.SINGLE_LINE_COMMENT, text, offset);
 
-  LanguageVersionToken.fromSubstring(
+  LanguageVersionTokenImpl.fromSubstring(
       String string, int start, int end, int tokenStart, this.major, this.minor,
       {bool canonicalize: false})
       : super.fromSubstring(
             TokenType.SINGLE_LINE_COMMENT, string, start, end, tokenStart,
             canonicalize: canonicalize);
 
-  LanguageVersionToken.fromUtf8Bytes(List<int> bytes, int start, int end,
+  LanguageVersionTokenImpl.fromUtf8Bytes(List<int> bytes, int start, int end,
       int tokenStart, this.major, this.minor)
       : super.fromUtf8Bytes(
             TokenType.SINGLE_LINE_COMMENT, bytes, start, end, true, tokenStart);
 }
 
-class DartDocToken extends CommentToken
-    implements analyzer.DocumentationCommentToken {
+class DartDocToken extends CommentTokenImpl
+    implements DocumentationCommentToken {
   /**
    * Creates a lazy comment token. If [canonicalize] is true, the string
    * is canonicalized before the token is created.
diff --git a/pkg/_fe_analyzer_shared/lib/src/scanner/utf8_bytes_scanner.dart b/pkg/_fe_analyzer_shared/lib/src/scanner/utf8_bytes_scanner.dart
index da22f90..a0fd026 100644
--- a/pkg/_fe_analyzer_shared/lib/src/scanner/utf8_bytes_scanner.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/scanner/utf8_bytes_scanner.dart
@@ -6,9 +6,9 @@
 
 import 'dart:convert' show unicodeBomCharacterRune, utf8;
 
-import 'token.dart' show SyntheticStringToken, TokenType;
+import 'token.dart' show LanguageVersionToken, SyntheticStringToken, TokenType;
 
-import 'token.dart' as analyzer show StringToken;
+import 'token.dart' as analyzer;
 
 import 'scanner.dart' show unicodeReplacementCharacter;
 
@@ -16,7 +16,11 @@
     show AbstractScanner, LanguageVersionChanged, ScannerConfiguration;
 
 import 'token_impl.dart'
-    show CommentToken, DartDocToken, LanguageVersionToken, StringToken;
+    show
+        CommentTokenImpl,
+        DartDocToken,
+        LanguageVersionTokenImpl,
+        StringTokenImpl;
 
 /**
  * Scanner that reads from a UTF-8 encoded list of bytes and creates tokens
@@ -227,7 +231,7 @@
   analyzer.StringToken createSubstringToken(
       TokenType type, int start, bool asciiOnly,
       [int extraOffset = 0]) {
-    return new StringToken.fromUtf8Bytes(
+    return new StringTokenImpl.fromUtf8Bytes(
         type, bytes, start, byteOffset + extraOffset, asciiOnly, tokenStart,
         precedingComments: comments);
   }
@@ -235,15 +239,17 @@
   @override
   analyzer.StringToken createSyntheticSubstringToken(
       TokenType type, int start, bool asciiOnly, String syntheticChars) {
-    String source = StringToken.decodeUtf8(bytes, start, byteOffset, asciiOnly);
+    String source =
+        StringTokenImpl.decodeUtf8(bytes, start, byteOffset, asciiOnly);
     return new SyntheticStringToken(
         type, source + syntheticChars, tokenStart, source.length);
   }
 
   @override
-  CommentToken createCommentToken(TokenType type, int start, bool asciiOnly,
+  analyzer.CommentToken createCommentToken(
+      TokenType type, int start, bool asciiOnly,
       [int extraOffset = 0]) {
-    return new CommentToken.fromUtf8Bytes(
+    return new CommentTokenImpl.fromUtf8Bytes(
         type, bytes, start, byteOffset + extraOffset, asciiOnly, tokenStart);
   }
 
@@ -257,7 +263,7 @@
   @override
   LanguageVersionToken createLanguageVersionToken(
       int start, int major, int minor) {
-    return new LanguageVersionToken.fromUtf8Bytes(
+    return new LanguageVersionTokenImpl.fromUtf8Bytes(
         bytes, start, byteOffset, tokenStart, major, minor);
   }
 
diff --git a/pkg/analyzer/lib/src/dart/analysis/file_state.dart b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
index 8d0f309..e2a6383 100644
--- a/pkg/analyzer/lib/src/dart/analysis/file_state.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
@@ -5,7 +5,7 @@
 import 'dart:typed_data';
 
 import 'package:_fe_analyzer_shared/src/scanner/token_impl.dart'
-    show StringToken;
+    show StringTokenImpl;
 import 'package:analyzer/dart/analysis/declared_variables.dart';
 import 'package:analyzer/dart/analysis/features.dart';
 import 'package:analyzer/dart/ast/ast.dart';
@@ -563,7 +563,7 @@
 
     // StringToken uses a static instance of StringCanonicalizer, so we need
     // to clear it explicitly once we are done using it for this file.
-    StringToken.canonicalizer.clear();
+    StringTokenImpl.canonicalizer.clear();
 
     return unit;
   }
diff --git a/pkg/analyzer/lib/src/dart/micro/library_graph.dart b/pkg/analyzer/lib/src/dart/micro/library_graph.dart
index d58d6bb..b36a098 100644
--- a/pkg/analyzer/lib/src/dart/micro/library_graph.dart
+++ b/pkg/analyzer/lib/src/dart/micro/library_graph.dart
@@ -877,7 +877,7 @@
 
     // StringToken uses a static instance of StringCanonicalizer, so we need
     // to clear it explicitly once we are done using it for this file.
-    StringToken.canonicalizer.clear();
+    StringTokenImpl.canonicalizer.clear();
 
     // TODO(scheglov) Use actual versions.
     unit.languageVersion = LibraryLanguageVersion(
diff --git a/pkg/analyzer/lib/src/fasta/ast_builder.dart b/pkg/analyzer/lib/src/fasta/ast_builder.dart
index e14406b..3438fa9 100644
--- a/pkg/analyzer/lib/src/fasta/ast_builder.dart
+++ b/pkg/analyzer/lib/src/fasta/ast_builder.dart
@@ -50,7 +50,7 @@
     show NullValue, StackListener;
 import 'package:_fe_analyzer_shared/src/scanner/errors.dart'
     show translateErrorToken;
-import 'package:_fe_analyzer_shared/src/scanner/scanner.dart' hide StringToken;
+import 'package:_fe_analyzer_shared/src/scanner/scanner.dart';
 import 'package:_fe_analyzer_shared/src/scanner/token.dart'
     show KeywordToken, StringToken, SyntheticStringToken, SyntheticToken;
 import 'package:_fe_analyzer_shared/src/scanner/token_constants.dart';
diff --git a/pkg/analyzer/lib/src/generated/engine.dart b/pkg/analyzer/lib/src/generated/engine.dart
index db1a627..ebe16a8 100644
--- a/pkg/analyzer/lib/src/generated/engine.dart
+++ b/pkg/analyzer/lib/src/generated/engine.dart
@@ -118,7 +118,7 @@
   /// will be performed the next time an analysis context is created.
   void clearCaches() {
     // See https://github.com/dart-lang/sdk/issues/30314.
-    StringToken.canonicalizer.clear();
+    StringTokenImpl.canonicalizer.clear();
   }
 }
 
diff --git a/pkg/analyzer/test/generated/parser_test_base.dart b/pkg/analyzer/test/generated/parser_test_base.dart
index e0c50bb..ef2d196 100644
--- a/pkg/analyzer/test/generated/parser_test_base.dart
+++ b/pkg/analyzer/test/generated/parser_test_base.dart
@@ -807,8 +807,8 @@
         null,
         null,
         Token(Keyword.CLASS, 0),
-        astFactory.simpleIdentifier(
-            fasta.StringToken.fromString(TokenType.IDENTIFIER, className, 6)),
+        astFactory.simpleIdentifier(fasta.StringTokenImpl.fromString(
+            TokenType.IDENTIFIER, className, 6)),
         null,
         null,
         null,
diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart
index f086339..d12efbd 100644
--- a/pkg/dart2wasm/lib/code_generator.dart
+++ b/pkg/dart2wasm/lib/code_generator.dart
@@ -388,6 +388,17 @@
     });
   }
 
+  /// Helper function to throw a Wasm ref downcast error.
+  void throwWasmRefError(String expected) {
+    wrap(
+        StringLiteral(expected),
+        translator
+            .translateType(translator.coreTypes.stringNonNullableRawType));
+    _call(translator.stackTraceCurrent.reference);
+    _call(translator.throwWasmRefError.reference);
+    b.unreachable();
+  }
+
   /// Generates code for an expression plus conversion code to convert the
   /// result to the expected type if needed. All expression code generation goes
   /// through this method.
@@ -1660,9 +1671,28 @@
   @override
   w.ValueType visitFunctionInvocation(
       FunctionInvocation node, w.ValueType expectedType) {
+    Expression receiver = node.receiver;
+    if (receiver is InstanceGet &&
+        receiver.interfaceTarget == translator.wasmFunctionCall) {
+      // Receiver is a WasmFunction
+      assert(receiver.name.text == "call");
+      w.RefType receiverType =
+          translator.translateType(dartTypeOf(receiver.receiver)) as w.RefType;
+      w.Local temp = addLocal(receiverType);
+      wrap(receiver.receiver, receiverType);
+      b.local_set(temp);
+      w.FunctionType functionType = receiverType.heapType as w.FunctionType;
+      assert(node.arguments.positional.length == functionType.inputs.length);
+      for (int i = 0; i < node.arguments.positional.length; i++) {
+        wrap(node.arguments.positional[i], functionType.inputs[i]);
+      }
+      b.local_get(temp);
+      b.call_ref();
+      return translator.outputOrVoid(functionType.outputs);
+    }
     int parameterCount = node.functionType?.requiredParameterCount ??
         node.arguments.positional.length;
-    return _functionCall(parameterCount, node.receiver, node.arguments);
+    return _functionCall(parameterCount, receiver, node.arguments);
   }
 
   w.ValueType _functionCall(
diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart
index 0fa9329..7fb0cf5 100644
--- a/pkg/dart2wasm/lib/intrinsics.dart
+++ b/pkg/dart2wasm/lib/intrinsics.dart
@@ -118,28 +118,45 @@
   Intrinsifier(this.codeGen);
 
   w.ValueType? generateInstanceGetterIntrinsic(InstanceGet node) {
-    DartType receiverType = dartTypeOf(node.receiver);
+    Expression receiver = node.receiver;
+    DartType receiverType = dartTypeOf(receiver);
     String name = node.name.text;
+    Member target = node.interfaceTarget;
+    Class cls = target.enclosingClass!;
+
+    // WasmAnyRef.isObject
+    if (cls == translator.wasmAnyRefClass) {
+      assert(name == "isObject");
+      w.Label succeed = b.block(const [], const [w.NumType.i32]);
+      w.Label fail = b.block(const [], const [w.RefType.any(nullable: false)]);
+      codeGen.wrap(receiver, w.RefType.any(nullable: false));
+      b.br_on_non_data(fail);
+      translator.ref_test(b, translator.topInfo);
+      b.br(succeed);
+      b.end(); // fail
+      b.drop();
+      b.i32_const(0);
+      b.end(); // succeed
+      return w.NumType.i32;
+    }
 
     // _WasmArray.length
-    if (node.interfaceTarget.enclosingClass == translator.wasmArrayBaseClass) {
+    if (cls == translator.wasmArrayBaseClass) {
       assert(name == 'length');
       DartType elementType =
           (receiverType as InterfaceType).typeArguments.single;
       w.ArrayType arrayType = translator.arrayTypeForDartType(elementType);
-      Expression array = node.receiver;
-      codeGen.wrap(array, w.RefType.def(arrayType, nullable: true));
+      codeGen.wrap(receiver, w.RefType.def(arrayType, nullable: true));
       b.array_len(arrayType);
       b.i64_extend_i32_u();
       return w.NumType.i64;
     }
 
     // int.bitlength
-    if (node.interfaceTarget.enclosingClass == translator.coreTypes.intClass &&
-        name == 'bitLength') {
+    if (cls == translator.coreTypes.intClass && name == 'bitLength') {
       w.Local temp = codeGen.function.addLocal(w.NumType.i64);
       b.i64_const(64);
-      codeGen.wrap(node.receiver, w.NumType.i64);
+      codeGen.wrap(receiver, w.NumType.i64);
       b.local_tee(temp);
       b.local_get(temp);
       b.i64_const(63);
@@ -151,28 +168,26 @@
     }
 
     // _HashAbstractImmutableBase._indexNullable
-    if (node.interfaceTarget == translator.immutableMapIndexNullable) {
+    if (target == translator.immutableMapIndexNullable) {
       ClassInfo info = translator.classInfo[translator.hashFieldBaseClass]!;
-      codeGen.wrap(node.receiver, info.nullableType);
+      codeGen.wrap(receiver, info.nullableType);
       b.struct_get(info.struct, FieldIndex.hashBaseIndex);
       return info.struct.fields[FieldIndex.hashBaseIndex].type.unpacked;
     }
 
     // _Compound._typedDataBase
-    if (node.interfaceTarget.enclosingClass == translator.ffiCompoundClass &&
-        name == '_typedDataBase') {
+    if (cls == translator.ffiCompoundClass && name == '_typedDataBase') {
       // A compound (subclass of Struct or Union) is represented by its i32
       // address. The _typedDataBase field contains a Pointer pointing to the
       // compound, whose representation is the same.
-      codeGen.wrap(node.receiver, w.NumType.i32);
+      codeGen.wrap(receiver, w.NumType.i32);
       return w.NumType.i32;
     }
 
     // Pointer.address
-    if (node.interfaceTarget.enclosingClass == translator.ffiPointerClass &&
-        name == 'address') {
+    if (cls == translator.ffiPointerClass && name == 'address') {
       // A Pointer is represented by its i32 address.
-      codeGen.wrap(node.receiver, w.NumType.i32);
+      codeGen.wrap(receiver, w.NumType.i32);
       b.i64_extend_i32_u();
       return w.NumType.i64;
     }
@@ -185,17 +200,17 @@
     DartType receiverType = dartTypeOf(receiver);
     String name = node.name.text;
     Procedure target = node.interfaceTarget;
+    Class cls = target.enclosingClass!;
 
     // _TypedListBase._setRange
-    if (target.enclosingClass == translator.typedListBaseClass &&
-        name == "_setRange") {
+    if (cls == translator.typedListBaseClass && name == "_setRange") {
       // Always fall back to alternative implementation.
       b.i32_const(0);
       return w.NumType.i32;
     }
 
     // _TypedList._(get|set)(Int|Uint|Float)(8|16|32|64)
-    if (node.interfaceTarget.enclosingClass == translator.typedListClass) {
+    if (cls == translator.typedListClass) {
       Match? match = RegExp("^_(get|set)(Int|Uint|Float)(8|16|32|64)\$")
           .matchAsPrefix(name);
       if (match != null) {
@@ -319,11 +334,24 @@
       }
     }
 
+    // WasmAnyRef.toObject
+    if (cls == translator.wasmAnyRefClass) {
+      assert(name == "toObject");
+      w.Label succeed = b.block(const [], [translator.topInfo.nonNullableType]);
+      w.Label fail = b.block(const [], const [w.RefType.any(nullable: false)]);
+      codeGen.wrap(receiver, w.RefType.any(nullable: false));
+      b.br_on_non_data(fail);
+      translator.br_on_cast(b, succeed, translator.topInfo);
+      b.end(); // fail
+      codeGen.throwWasmRefError("a Dart object");
+      b.end(); // succeed
+      return translator.topInfo.nonNullableType;
+    }
+
     // WasmIntArray.(readSigned|readUnsigned|write)
     // WasmFloatArray.(read|write)
     // WasmObjectArray.(read|write)
-    if (node.interfaceTarget.enclosingClass?.superclass ==
-        translator.wasmArrayBaseClass) {
+    if (cls.superclass == translator.wasmArrayBaseClass) {
       DartType elementType =
           (receiverType as InterfaceType).typeArguments.single;
       w.ArrayType arrayType = translator.arrayTypeForDartType(elementType);
@@ -388,10 +416,8 @@
     }
 
     // Wasm(I32|I64|F32|F64) conversions
-    if (node.interfaceTarget.enclosingClass?.superclass?.superclass ==
-        translator.wasmTypesBaseClass) {
-      w.StorageType receiverType =
-          translator.builtinTypes[node.interfaceTarget.enclosingClass]!;
+    if (cls.superclass?.superclass == translator.wasmTypesBaseClass) {
+      w.StorageType receiverType = translator.builtinTypes[cls]!;
       switch (receiverType) {
         case w.NumType.i32:
           assert(name == "toIntSigned" || name == "toIntUnsigned");
@@ -557,6 +583,7 @@
 
   w.ValueType? generateStaticIntrinsic(StaticInvocation node) {
     String name = node.name.text;
+    Class? cls = node.target.enclosingClass;
 
     // dart:core static functions
     if (node.target.enclosingLibrary == translator.coreTypes.coreLibrary) {
@@ -710,7 +737,7 @@
           b.f64_reinterpret_i64();
           return w.NumType.f64;
         case "getID":
-          assert(node.target.enclosingClass?.name == "ClassID");
+          assert(cls?.name == "ClassID");
           ClassInfo info = translator.topInfo;
           codeGen.wrap(node.arguments.positional.single, info.nullableType);
           b.struct_get(info.struct, FieldIndex.classId);
@@ -836,24 +863,58 @@
       }
     }
 
-    // Wasm(Int|Float|Object)Array constructors
-    if (node.target.enclosingClass?.superclass ==
-        translator.wasmArrayBaseClass) {
-      Expression length = node.arguments.positional[0];
-      w.ArrayType arrayType =
-          translator.arrayTypeForDartType(node.arguments.types.single);
-      codeGen.wrap(length, w.NumType.i64);
-      b.i32_wrap_i64();
-      translator.array_new_default(b, arrayType);
-      return w.RefType.def(arrayType, nullable: false);
-    }
+    if (cls != null && translator.isWasmType(cls)) {
+      // Wasm(Int|Float|Object)Array constructors
+      if (cls.superclass == translator.wasmArrayBaseClass) {
+        Expression length = node.arguments.positional[0];
+        w.ArrayType arrayType =
+            translator.arrayTypeForDartType(node.arguments.types.single);
+        codeGen.wrap(length, w.NumType.i64);
+        b.i32_wrap_i64();
+        translator.array_new_default(b, arrayType);
+        return w.RefType.def(arrayType, nullable: false);
+      }
 
-    // Wasm(I32|I64|F32|F64) constructors
-    if (node.target.enclosingClass?.superclass?.superclass ==
-        translator.wasmTypesBaseClass) {
+      // (WasmFuncRef|WasmFunction).fromRef constructors
+      if ((cls == translator.wasmFuncRefClass ||
+              cls == translator.wasmFunctionClass) &&
+          name == "fromRef") {
+        Expression ref = node.arguments.positional[0];
+        w.RefType resultType = typeOfExp(node) as w.RefType;
+        w.Label succeed = b.block(const [], [resultType]);
+        w.Label fail =
+            b.block(const [], const [w.RefType.any(nullable: false)]);
+        codeGen.wrap(ref, w.RefType.any(nullable: false));
+        b.br_on_non_func(fail);
+        if (cls == translator.wasmFunctionClass) {
+          assert(resultType.heapType is w.FunctionType);
+          translator.br_on_cast_fail(b, fail, resultType.heapType);
+        }
+        b.br(succeed);
+        b.end(); // fail
+        codeGen.throwWasmRefError("a function with the expected signature");
+        b.end(); // succeed
+        return resultType;
+      }
+
+      // WasmFunction.fromFunction constructor
+      if (cls == translator.wasmFunctionClass) {
+        assert(name == "fromFunction");
+        Expression f = node.arguments.positional[0];
+        if (f is! ConstantExpression || f.constant is! StaticTearOffConstant) {
+          throw "Argument to WasmFunction.fromFunction isn't a static function";
+        }
+        StaticTearOffConstant func = f.constant as StaticTearOffConstant;
+        w.BaseFunction wasmFunction =
+            translator.functions.getFunction(func.targetReference);
+        w.Global functionRef = translator.makeFunctionRef(wasmFunction);
+        b.global_get(functionRef);
+        return functionRef.type.type;
+      }
+
+      // Wasm(AnyRef|FuncRef|EqRef|DataRef|I32|I64|F32|F64) constructors
       Expression value = node.arguments.positional[0];
-      w.StorageType targetType =
-          translator.builtinTypes[node.target.enclosingClass]!;
+      w.StorageType targetType = translator.builtinTypes[cls]!;
       switch (targetType) {
         case w.NumType.i32:
           codeGen.wrap(value, w.NumType.i64);
@@ -869,6 +930,10 @@
         case w.NumType.f64:
           codeGen.wrap(value, w.NumType.f64);
           return w.NumType.f64;
+        default:
+          w.RefType valueType = targetType as w.RefType;
+          codeGen.wrap(value, valueType);
+          return valueType;
       }
     }
 
diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart
index 1eacf81..2a61d3c 100644
--- a/pkg/dart2wasm/lib/translator.dart
+++ b/pkg/dart2wasm/lib/translator.dart
@@ -67,8 +67,10 @@
   late final Class wasmTypesBaseClass;
   late final Class wasmArrayBaseClass;
   late final Class wasmAnyRefClass;
+  late final Class wasmFuncRefClass;
   late final Class wasmEqRefClass;
   late final Class wasmDataRefClass;
+  late final Class wasmFunctionClass;
   late final Class boxedBoolClass;
   late final Class boxedIntClass;
   late final Class boxedDoubleClass;
@@ -91,11 +93,13 @@
   late final Class typedListViewClass;
   late final Class byteDataViewClass;
   late final Class typeErrorClass;
+  late final Procedure wasmFunctionCall;
   late final Procedure stackTraceCurrent;
   late final Procedure stringEquals;
   late final Procedure stringInterpolate;
   late final Procedure throwNullCheckError;
   late final Procedure throwAsCheckError;
+  late final Procedure throwWasmRefError;
   late final Procedure mapFactory;
   late final Procedure mapPut;
   late final Procedure immutableMapIndexNullable;
@@ -130,7 +134,7 @@
   final Map<int, w.StructType> functionTypeCache = {};
   final Map<w.StructType, int> functionTypeParameterCount = {};
   final Map<int, w.DefinedGlobal> functionTypeRtt = {};
-  final Map<w.DefinedFunction, w.DefinedGlobal> functionRefCache = {};
+  final Map<w.BaseFunction, w.DefinedGlobal> functionRefCache = {};
   final Map<Procedure, w.DefinedFunction> tearOffFunctionCache = {};
 
   ClassInfo get topInfo => classes[0];
@@ -161,8 +165,10 @@
     wasmTypesBaseClass = lookupWasm("_WasmBase");
     wasmArrayBaseClass = lookupWasm("_WasmArray");
     wasmAnyRefClass = lookupWasm("WasmAnyRef");
+    wasmFuncRefClass = lookupWasm("WasmFuncRef");
     wasmEqRefClass = lookupWasm("WasmEqRef");
     wasmDataRefClass = lookupWasm("WasmDataRef");
+    wasmFunctionClass = lookupWasm("WasmFunction");
     boxedBoolClass = lookupCore("_BoxedBool");
     boxedIntClass = lookupCore("_BoxedInt");
     boxedDoubleClass = lookupCore("_BoxedDouble");
@@ -185,6 +191,8 @@
     typedListClass = lookupTypedData("_TypedList");
     typedListViewClass = lookupTypedData("_TypedListView");
     byteDataViewClass = lookupTypedData("_ByteDataView");
+    wasmFunctionCall =
+        wasmFunctionClass.procedures.firstWhere((p) => p.name.text == "call");
     stackTraceCurrent =
         stackTraceClass.procedures.firstWhere((p) => p.name.text == "current");
     stringEquals =
@@ -195,6 +203,8 @@
         .firstWhere((p) => p.name.text == "_throwNullCheckError");
     throwAsCheckError = typeErrorClass.procedures
         .firstWhere((p) => p.name.text == "_throwAsCheckError");
+    throwWasmRefError = typeErrorClass.procedures
+        .firstWhere((p) => p.name.text == "_throwWasmRefError");
     mapFactory = lookupCollection("LinkedHashMap").procedures.firstWhere(
         (p) => p.kind == ProcedureKind.Factory && p.name.text == "_default");
     mapPut = lookupCollection("_CompactLinkedCustomHashMap")
@@ -208,9 +218,10 @@
       coreTypes.boolClass: w.NumType.i32,
       coreTypes.intClass: w.NumType.i64,
       coreTypes.doubleClass: w.NumType.f64,
-      wasmAnyRefClass: w.RefType.any(nullable: false),
-      wasmEqRefClass: w.RefType.eq(nullable: false),
-      wasmDataRefClass: w.RefType.data(nullable: false),
+      wasmAnyRefClass: const w.RefType.any(nullable: false),
+      wasmFuncRefClass: const w.RefType.func(nullable: false),
+      wasmEqRefClass: const w.RefType.eq(nullable: false),
+      wasmDataRefClass: const w.RefType.data(nullable: false),
       boxedBoolClass: w.NumType.i32,
       boxedIntClass: w.NumType.i64,
       boxedDoubleClass: w.NumType.f64,
@@ -421,6 +432,28 @@
         return w.RefType.def(arrayTypeForDartType(elementType),
             nullable: false);
       }
+      if (type.classNode == wasmFunctionClass) {
+        DartType functionType = type.typeArguments.single;
+        if (functionType is! FunctionType) {
+          throw "The type argument of a WasmFunction must be a function type";
+        }
+        if (functionType.typeParameters.isNotEmpty ||
+            functionType.namedParameters.isNotEmpty ||
+            functionType.requiredParameterCount !=
+                functionType.positionalParameters.length) {
+          throw "A WasmFunction can't have optional/type parameters";
+        }
+        List<w.ValueType> inputs = [
+          for (DartType type in functionType.positionalParameters)
+            translateType(type)
+        ];
+        List<w.ValueType> outputs = [
+          if (functionType.returnType != const VoidType())
+            translateType(functionType.returnType)
+        ];
+        w.FunctionType wasmType = this.functionType(inputs, outputs);
+        return w.RefType.def(wasmType, nullable: type.isPotentiallyNullable);
+      }
       return typeForInfo(
           classInfo[type.classNode]!, type.isPotentiallyNullable);
     }
@@ -498,7 +531,7 @@
     return functionTypeParameterCount[heapType]!;
   }
 
-  w.DefinedGlobal makeFunctionRef(w.DefinedFunction f) {
+  w.DefinedGlobal makeFunctionRef(w.BaseFunction f) {
     return functionRefCache.putIfAbsent(f, () {
       w.DefinedGlobal global = m.addGlobal(
           w.GlobalType(w.RefType.def(f.type, nullable: false), mutable: false));
@@ -605,11 +638,16 @@
         b.ref_as_non_null();
       } else {
         // Downcast
-        var heapType = (to as w.RefType).heapType;
-        ClassInfo? info = classForHeapType[heapType];
         if (from.nullable && !to.nullable) {
           b.ref_as_non_null();
         }
+        var heapType = (to as w.RefType).heapType;
+        if (heapType is w.FunctionType) {
+          b.ref_as_func();
+          ref_cast(b, heapType);
+          return;
+        }
+        ClassInfo? info = classForHeapType[heapType];
         if (!(from as w.RefType).heapType.isSubtypeOf(w.HeapType.data)) {
           b.ref_as_data();
         }
@@ -711,7 +749,7 @@
   // The [type] parameter taken by the methods is either a [ClassInfo] (to use
   // the RTT for the class), an [int] (to use the RTT for the closure struct
   // corresponding to functions with that number of parameters) or a
-  // [w.DataType] (to use the canonical RTT for the type).
+  // [w.DefType] (to use the canonical RTT for the type).
 
   void struct_new(w.Instructions b, Object type) {
     if (options.runtimeTypes) {
@@ -823,7 +861,7 @@
       }
       return struct;
     } else {
-      b.rtt_canon(type as w.DataType);
+      b.rtt_canon(type as w.DefType);
       return type;
     }
   }
diff --git a/pkg/front_end/lib/src/fasta/compiler_context.dart b/pkg/front_end/lib/src/fasta/compiler_context.dart
index 9d86de3..0ace047 100644
--- a/pkg/front_end/lib/src/fasta/compiler_context.dart
+++ b/pkg/front_end/lib/src/fasta/compiler_context.dart
@@ -12,7 +12,7 @@
 import 'package:_fe_analyzer_shared/src/util/colors.dart' as colors;
 
 import 'package:_fe_analyzer_shared/src/scanner/token_impl.dart'
-    show StringToken;
+    show StringTokenImpl;
 
 import 'package:kernel/ast.dart' show Source;
 
@@ -137,7 +137,7 @@
   }
 
   void clear() {
-    StringToken.canonicalizer.clear();
+    StringTokenImpl.canonicalizer.clear();
     errors.clear();
     dependencies.clear();
   }
diff --git a/pkg/front_end/test/fasta/parser/token_stream_rewriter_test.dart b/pkg/front_end/test/fasta/parser/token_stream_rewriter_test.dart
index 6828c746..6d01c32 100644
--- a/pkg/front_end/test/fasta/parser/token_stream_rewriter_test.dart
+++ b/pkg/front_end/test/fasta/parser/token_stream_rewriter_test.dart
@@ -6,7 +6,7 @@
 import 'package:_fe_analyzer_shared/src/scanner/scanner.dart'
     show ScannerResult, scanString;
 import 'package:_fe_analyzer_shared/src/scanner/token.dart'
-    show ReplacementToken, Token, TokenType;
+    show ReplacementToken, StringToken, Token, TokenType;
 import 'package:_fe_analyzer_shared/src/scanner/token_impl.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -162,7 +162,7 @@
   void test_replaceNextTokenWithSyntheticToken_1() {
     Token a = _makeToken(0, 'a');
     StringToken b = _makeToken(5, 'b');
-    b.precedingComments = new CommentToken.fromSubstring(
+    b.precedingComments = new CommentTokenImpl.fromSubstring(
         TokenType.SINGLE_LINE_COMMENT, "Test comment", 1, 9, 1,
         canonicalize: true);
     Token c = _makeToken(10, 'c');
@@ -186,7 +186,7 @@
   void test_replaceNextTokenWithSyntheticToken_2() {
     Token a = _makeToken(0, 'a');
     StringToken b = _makeToken(5, 'b');
-    b.precedingComments = new CommentToken.fromSubstring(
+    b.precedingComments = new CommentTokenImpl.fromSubstring(
         TokenType.SINGLE_LINE_COMMENT, "Test comment", 1, 9, 1,
         canonicalize: true);
     _link([a, b]);
@@ -208,7 +208,7 @@
   void test_replaceNextTokensWithSyntheticToken_1() {
     Token a = _makeToken(0, 'a');
     StringToken b = _makeToken(5, 'b');
-    b.precedingComments = new CommentToken.fromSubstring(
+    b.precedingComments = new CommentTokenImpl.fromSubstring(
         TokenType.SINGLE_LINE_COMMENT, "Test comment", 1, 9, 1,
         canonicalize: true);
     Token c = _makeToken(10, 'c');
@@ -236,7 +236,7 @@
   void test_replaceNextTokensWithSyntheticToken_2() {
     Token a = _makeToken(0, 'a');
     StringToken b = _makeToken(5, 'b');
-    b.precedingComments = new CommentToken.fromSubstring(
+    b.precedingComments = new CommentTokenImpl.fromSubstring(
         TokenType.SINGLE_LINE_COMMENT, "Test comment", 1, 9, 1,
         canonicalize: true);
     Token c = _makeToken(10, 'c');
@@ -338,7 +338,8 @@
   }
 
   StringToken _makeToken(int charOffset, String text) {
-    return new StringToken.fromString(TokenType.IDENTIFIER, text, charOffset);
+    return new StringTokenImpl.fromString(
+        TokenType.IDENTIFIER, text, charOffset);
   }
 }
 
diff --git a/pkg/front_end/test/parser_suite.dart b/pkg/front_end/test/parser_suite.dart
index 9ed7b26..c73cb53 100644
--- a/pkg/front_end/test/parser_suite.dart
+++ b/pkg/front_end/test/parser_suite.dart
@@ -406,7 +406,10 @@
         }
       }
       if (addTypes) {
-        sb.write("[${token.runtimeType}]");
+        // Avoid 6000+ changes caused by "Impl" being added to some token
+        // classes.
+        String type = token.runtimeType.toString().replaceFirst("Impl", "");
+        sb.write("[$type]");
       }
       printed = true;
       endOfLast = token.end;
diff --git a/pkg/wasm_builder/lib/src/instructions.dart b/pkg/wasm_builder/lib/src/instructions.dart
index 10df25a..6c04e05 100644
--- a/pkg/wasm_builder/lib/src/instructions.dart
+++ b/pkg/wasm_builder/lib/src/instructions.dart
@@ -1234,7 +1234,8 @@
   /// Emit a `br_on_cast_static_fail` instruction.
   void br_on_cast_static_fail(Label label, DefType targetType) {
     assert(_verifyBranchTypes(label, 1, [_topOfStack]));
-    assert(_verifyCast((inputs) => [RefType.def(targetType, nullable: false)],
+    assert(_verifyCastStatic(
+        (inputs) => [RefType.def(targetType, nullable: false)],
         trace: ['br_on_cast_static_fail', label, targetType]));
     writeBytes(const [0xFB, 0x47]);
     _writeLabel(label);
diff --git a/sdk/lib/_internal/wasm/lib/errors_patch.dart b/sdk/lib/_internal/wasm/lib/errors_patch.dart
index d560ac0..666f279 100644
--- a/sdk/lib/_internal/wasm/lib/errors_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/errors_patch.dart
@@ -65,4 +65,11 @@
         stackTrace);
     return _throwObjectWithStackTrace(typeError, stackTrace);
   }
+
+  @pragma("wasm:entry-point")
+  static Never _throwWasmRefError(String expected, StackTrace stackTrace) {
+    final typeError = _TypeError.fromMessageAndStackTrace(
+        "The Wasm reference is not $expected", stackTrace);
+    return _throwObjectWithStackTrace(typeError, stackTrace);
+  }
 }
diff --git a/sdk/lib/wasm/wasm_types.dart b/sdk/lib/wasm/wasm_types.dart
index 288deaa..ea09211 100644
--- a/sdk/lib/wasm/wasm_types.dart
+++ b/sdk/lib/wasm/wasm_types.dart
@@ -23,17 +23,49 @@
 
 /// The Wasm `anyref` type.
 @pragma("wasm:entry-point")
-class WasmAnyRef extends _WasmBase {}
+class WasmAnyRef extends _WasmBase {
+  /// Upcast Dart object to `anyref`.
+  external factory WasmAnyRef.fromObject(Object o);
+
+  /// Whether this reference is a Dart object.
+  external bool get isObject;
+
+  /// Downcast `anyref` to a Dart object.
+  ///
+  /// Will throw if the reference is not a Dart object.
+  external Object toObject();
+}
+
+/// The Wasm `funcref` type.
+@pragma("wasm:entry-point")
+class WasmFuncRef extends WasmAnyRef {
+  /// Upcast typed function reference to `funcref`
+  external factory WasmFuncRef.fromWasmFunction(WasmFunction<Function> fun);
+
+  /// Downcast `anyref` to `funcref`.
+  ///
+  /// Will throw if the reference is not a `funcref`.
+  external factory WasmFuncRef.fromRef(WasmAnyRef ref);
+}
 
 /// The Wasm `eqref` type.
 @pragma("wasm:entry-point")
-class WasmEqRef extends WasmAnyRef {}
+class WasmEqRef extends WasmAnyRef {
+  /// Upcast Dart object to `eqref`.
+  external factory WasmEqRef.fromObject(Object o);
+}
 
 /// The Wasm `dataref` type.
 @pragma("wasm:entry-point")
-class WasmDataRef extends WasmEqRef {}
+class WasmDataRef extends WasmEqRef {
+  /// Upcast Dart object to `dataref`.
+  external factory WasmDataRef.fromObject(Object o);
+}
 
 abstract class _WasmArray extends WasmDataRef {
+  /// Dummy factory to silence error about missing superclass constructor.
+  external factory _WasmArray._dummy();
+
   external int get length;
 }
 
@@ -102,6 +134,25 @@
   external void write(int index, T value);
 }
 
+/// Wasm typed function reference.
+@pragma("wasm:entry-point")
+class WasmFunction<F extends Function> extends WasmFuncRef {
+  /// Create a typed function reference referring to the given function.
+  ///
+  /// The argument must directly name a static function with no optional
+  /// parameters and no type parameters.
+  external factory WasmFunction.fromFunction(F f);
+
+  /// Downcast `anyref` to a typed function reference.
+  ///
+  /// Will throw if the reference is not a function with the expected signature.
+  external factory WasmFunction.fromRef(WasmAnyRef ref);
+
+  /// Call the function referred to by this typed function reference.
+  @pragma("wasm:entry-point")
+  external F get call;
+}
+
 extension IntToWasmInt on int {
   WasmI32 toWasmI32() => WasmI32.fromInt(this);
   WasmI64 toWasmI64() => WasmI64.fromInt(this);
diff --git a/tests/web/wasm/wasm_types_test.dart b/tests/web/wasm/wasm_types_test.dart
new file mode 100644
index 0000000..5538605
--- /dev/null
+++ b/tests/web/wasm/wasm_types_test.dart
@@ -0,0 +1,110 @@
+// 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 'dart:wasm';
+
+import 'package:expect/expect.dart';
+
+@pragma("wasm:import", "Object.create")
+external WasmAnyRef createObject(WasmAnyRef? prototype);
+
+@pragma("wasm:import", "Array.of")
+external WasmAnyRef singularArray(WasmAnyRef element);
+
+@pragma("wasm:import", "Reflect.apply")
+external WasmAnyRef apply(
+    WasmAnyRef target, WasmAnyRef thisArgument, WasmAnyRef argumentsList);
+
+WasmAnyRef? anyRef;
+WasmEqRef? eqRef;
+WasmDataRef? dataRef;
+
+int funCount = 0;
+
+void fun(WasmEqRef arg) {
+  funCount++;
+  Expect.equals("Dart object", arg.toObject());
+}
+
+test() {
+  // Some test objects
+  Object dartObject1 = "1";
+  Object dartObject2 = true;
+  Object dartObject3 = Object();
+  WasmAnyRef jsObject1 = createObject(null);
+
+  // A JS object is not a Dart object.
+  Expect.isFalse(jsObject1.isObject);
+
+  // A Wasm ref can be null and can be checked for null.
+  WasmAnyRef? jsObject2 = null;
+  Expect.isTrue(jsObject2 == null);
+
+  // Upcast Dart objects to Wasm refs and put them in fields.
+  anyRef = WasmAnyRef.fromObject(dartObject1);
+  eqRef = WasmEqRef.fromObject(dartObject2);
+  dataRef = WasmDataRef.fromObject(dartObject3);
+
+  // Dart objects are Dart objects.
+  Expect.isTrue(anyRef!.isObject);
+  Expect.isTrue(eqRef!.isObject);
+  Expect.isTrue(dataRef!.isObject);
+
+  // Casting back yields the original objects.
+  Expect.identical(dartObject1, anyRef!.toObject());
+  Expect.identical(dartObject2, eqRef!.toObject());
+  Expect.identical(dartObject3, dataRef!.toObject());
+
+  // Casting a JS object to a Dart object throws.
+  Object o;
+  Expect.throws(() {
+    o = jsObject1.toObject();
+  }, (_) => true);
+
+  // Integer and float conversions
+  Expect.equals(1, 1.toWasmI32().toIntSigned());
+  Expect.equals(-2, (-2).toWasmI32().toIntSigned());
+  Expect.equals(3, 3.toWasmI32().toIntUnsigned());
+  Expect.notEquals(-4, (-4).toWasmI32().toIntUnsigned());
+  Expect.equals(5, 5.toWasmI64().toInt());
+  Expect.equals(6.0, 6.0.toWasmF32().toDouble());
+  Expect.notEquals(7.1, 7.1.toWasmF32().toDouble());
+  Expect.equals(8.0, 8.0.toWasmF64().toDouble());
+
+  // Create a typed function reference for a Dart function and call it, both
+  // directly and from JS.
+  var dartObjectRef = WasmEqRef.fromObject("Dart object");
+  var ff = WasmFunction.fromFunction(fun);
+  ff.call(dartObjectRef);
+  apply(ff, createObject(null), singularArray(dartObjectRef));
+  Expect.isFalse(ff.isObject);
+
+  // Cast a typed function reference to a `funcref` and back.
+  WasmFuncRef funcref = WasmFuncRef.fromWasmFunction(ff);
+  var ff2 = WasmFunction<void Function(WasmEqRef)>.fromRef(funcref);
+  ff2.call(dartObjectRef);
+  Expect.isFalse(ff2.isObject);
+
+  // Casting a non-function JS object to a typed function reference throws.
+  Expect.throws(() {
+    WasmFunction<double Function(double)>.fromRef(jsObject1);
+  }, (_) => true);
+
+  // Create a typed function reference from an import and call it.
+  var createObjectFun = WasmFunction.fromFunction(createObject);
+  WasmAnyRef jsObject3 = createObjectFun.call(null);
+  Expect.isFalse(jsObject3.isObject);
+
+  Expect.equals(3, funCount);
+}
+
+main() {
+  try {
+    test();
+  } catch (e, s) {
+    print(e);
+    print(s);
+    rethrow;
+  }
+}
diff --git a/tools/VERSION b/tools/VERSION
index 6c9080c..5aa9356 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 18
 PATCH 0
-PRERELEASE 34
+PRERELEASE 35
 PRERELEASE_PATCH 0
\ No newline at end of file