Version 2.19.0-389.0.dev

Merge 4306e077756f4bef3cf1b3ed26b6bc5e1c08e243 into dev
diff --git a/WATCHLISTS b/WATCHLISTS
index be0e2b3..49b1558 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -34,6 +34,14 @@
         '^tests/web'
       )
     },
+    'dart2wasm': {
+      'filepath': (
+        '^pkg/dart2wasm|'
+        '^pkg/wasm_builder|'
+        '^sdk/lib/_internal/vm_shared|'
+        '^sdk/lib/_internal/wasm'
+      )
+    },
     'dartdevc': {
       'filepath': (
         '^pkg/dev_compiler|'
@@ -76,6 +84,7 @@
 
   'WATCHLISTS': {
     'dart2js': [ 'dart2js-team+reviews@google.com' ],
+    'dart2wasm': [ 'dart2wasm-team+reviews@google.com' ],
     'dartdevc': [ 'dart-dc-team+reviews@google.com' ],
     'experimental_features': [ 'scheglov@google.com' ],
     'front_end': [ 'dart-fe-team+reviews@google.com' ],
diff --git a/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart b/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart
index 7f51503..e3a006c 100644
--- a/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart
@@ -29,6 +29,12 @@
   NamedType(this.name, this.type);
 }
 
+/// Information supplied by the client to [TypeAnalyzer.analyzeObjectPattern],
+/// [TypeAnalyzer.analyzeRecordPattern], or
+/// [TypeAnalyzer.analyzeRecordPatternSchema] about a single field in a record
+/// or object pattern.
+///
+/// The client is free to `implement` or `extend` this class.
 class RecordPatternField<Node extends Object, Pattern extends Object> {
   /// The client specific node from which this object was created.  It can be
   /// used for error reporting.
diff --git a/pkg/_fe_analyzer_shared/test/mini_ast.dart b/pkg/_fe_analyzer_shared/test/mini_ast.dart
index 3567b7e..d71ed9e 100644
--- a/pkg/_fe_analyzer_shared/test/mini_ast.dart
+++ b/pkg/_fe_analyzer_shared/test/mini_ast.dart
@@ -240,7 +240,7 @@
 
 Pattern objectPattern({
   required ObjectPatternRequiredType requiredType,
-  required List<shared.RecordPatternField<Node, Pattern>> fields,
+  required List<RecordPatternField> fields,
 }) {
   return _ObjectPattern(
     requiredType: requiredType,
@@ -249,7 +249,7 @@
   );
 }
 
-Pattern recordPattern(List<SharedRecordPatternField> fields) =>
+Pattern recordPattern(List<RecordPatternField> fields) =>
     _RecordPattern(fields, location: computeLocation());
 
 Pattern relationalPattern(
@@ -297,8 +297,6 @@
     _VariablePattern(type == null ? null : Type(type), null, expectInferredType,
         isFinal: isFinal, location: computeLocation());
 
-typedef SharedRecordPatternField = shared.RecordPatternField<Node, Pattern>;
-
 mixin CaseHead implements CaseHeads, Node {
   @override
   List<CaseHead> get _caseHeads => [this];
@@ -1130,6 +1128,14 @@
   void preVisit(
       PreVisitor visitor, VariableBinder<Node, Var, Type> variableBinder);
 
+  RecordPatternField recordField([String? name]) {
+    return RecordPatternField(
+      name: name,
+      pattern: this,
+      location: computeLocation(),
+    );
+  }
+
   @override
   String toString() => _debugString(needsKeywordOrType: true);
 
@@ -1175,10 +1181,20 @@
   PromotableLValue._({required super.location}) : super._();
 }
 
-/// TODO(scheglov) This node is used temporary to model 'node'.
-class RecordPatternField implements Node {
+/// A field in object and record patterns.
+class RecordPatternField extends Node
+    implements shared.RecordPatternField<Node, Pattern> {
+  final String? name;
+  final Pattern pattern;
+
+  RecordPatternField({
+    required this.name,
+    required this.pattern,
+    required super.location,
+  }) : super._();
+
   @override
-  dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
+  Node get node => this;
 }
 
 /// Representation of a statement in the pseudo-Dart language used for flow
@@ -3216,7 +3232,7 @@
   @override
   Type resolveObjectPatternPropertyGet({
     required Type receiverType,
-    required SharedRecordPatternField field,
+    required shared.RecordPatternField<Node, Pattern> field,
   }) {
     return _harness.getMember(receiverType, field.name!)._type;
   }
@@ -3390,7 +3406,7 @@
 
 class _ObjectPattern extends Pattern {
   final ObjectPatternRequiredType requiredType;
-  final List<SharedRecordPatternField> fields;
+  final List<RecordPatternField> fields;
 
   _ObjectPattern({
     required this.requiredType,
@@ -3526,7 +3542,7 @@
 }
 
 class _RecordPattern extends Pattern {
-  final List<SharedRecordPatternField> fields;
+  final List<RecordPatternField> fields;
 
   _RecordPattern(this.fields, {required super.location}) : super._();
 
diff --git a/pkg/_fe_analyzer_shared/test/type_inference/type_inference_test.dart b/pkg/_fe_analyzer_shared/test/type_inference/type_inference_test.dart
index d3bb183..c0286d1 100644
--- a/pkg/_fe_analyzer_shared/test/type_inference/type_inference_test.dart
+++ b/pkg/_fe_analyzer_shared/test/type_inference/type_inference_test.dart
@@ -4,8 +4,6 @@
 
 import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer.dart'
     hide RecordPatternField, NamedType, RecordType;
-import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer.dart'
-    as shared;
 import 'package:test/test.dart';
 
 import '../mini_ast.dart';
@@ -1838,11 +1836,7 @@
               objectPattern(
                 requiredType: ObjectPatternRequiredType.name('B'),
                 fields: [
-                  shared.RecordPatternField(
-                    node: RecordPatternField(),
-                    name: 'foo',
-                    pattern: Var('foo').pattern(),
-                  ),
+                  Var('foo').pattern().recordField('foo'),
                 ],
               ),
               [],
@@ -1861,11 +1855,7 @@
               objectPattern(
                 requiredType: ObjectPatternRequiredType.type('num'),
                 fields: [
-                  shared.RecordPatternField(
-                    node: RecordPatternField(),
-                    name: 'foo',
-                    pattern: Var('foo').pattern(),
-                  ),
+                  Var('foo').pattern().recordField('foo'),
                 ],
               ),
               expr('int').checkContext('num'),
@@ -1882,11 +1872,7 @@
               objectPattern(
                 requiredType: ObjectPatternRequiredType.type('int'),
                 fields: [
-                  shared.RecordPatternField(
-                    node: RecordPatternField(),
-                    name: 'foo',
-                    pattern: Var('foo').pattern(),
-                  ),
+                  Var('foo').pattern().recordField('foo'),
                 ],
               )..errorId = 'PATTERN',
               expr('num').checkContext('int'),
@@ -1910,16 +1896,8 @@
               ifCase(
                 expr('dynamic').checkContext('?'),
                 recordPattern([
-                  shared.RecordPatternField(
-                    node: RecordPatternField(),
-                    name: null,
-                    pattern: Var('a').pattern(type: 'int'),
-                  ),
-                  shared.RecordPatternField(
-                    node: RecordPatternField(),
-                    name: null,
-                    pattern: Var('b').pattern(),
-                  ),
+                  Var('a').pattern(type: 'int').recordField(),
+                  Var('b').pattern().recordField(),
                 ]),
                 [],
               ).checkIr(
@@ -1943,16 +1921,8 @@
               h.run([
                 match(
                   recordPattern([
-                    shared.RecordPatternField(
-                      node: RecordPatternField(),
-                      name: null,
-                      pattern: Var('a').pattern(type: 'int'),
-                    ),
-                    shared.RecordPatternField(
-                      node: RecordPatternField(),
-                      name: null,
-                      pattern: Var('b').pattern(),
-                    ),
+                    Var('a').pattern(type: 'int').recordField(),
+                    Var('b').pattern().recordField(),
                   ]),
                   expr('(int, String)').checkContext('(int, ?)'),
                 ).checkIr(
@@ -1970,17 +1940,9 @@
               h.run([
                 (match(
                   recordPattern([
-                    shared.RecordPatternField(
-                      node: RecordPatternField(),
-                      name: null,
-                      pattern: Var('a').pattern(type: 'int')
-                        ..errorId = 'VAR(a)',
-                    ),
-                    shared.RecordPatternField(
-                      node: RecordPatternField(),
-                      name: null,
-                      pattern: Var('b').pattern(),
-                    ),
+                    (Var('a').pattern(type: 'int')..errorId = 'VAR(a)')
+                        .recordField(),
+                    Var('b').pattern().recordField(),
                   ])
                     ..errorId = 'PATTERN',
                   expr('(int,)').checkContext('(int, ?)'),
@@ -2005,16 +1967,8 @@
                   ifCase(
                     expr('(int,)').checkContext('?'),
                     recordPattern([
-                      shared.RecordPatternField(
-                        node: RecordPatternField(),
-                        name: null,
-                        pattern: Var('a').pattern(),
-                      ),
-                      shared.RecordPatternField(
-                        node: RecordPatternField(),
-                        name: null,
-                        pattern: Var('b').pattern(),
-                      ),
+                      Var('a').pattern().recordField(),
+                      Var('b').pattern().recordField(),
                     ]),
                     [],
                   ).checkIr('ifCase(expr((int)), recordPattern(varPattern(a, '
@@ -2029,11 +1983,7 @@
                   ifCase(
                     expr('(int, String)').checkContext('?'),
                     recordPattern([
-                      shared.RecordPatternField(
-                        node: RecordPatternField(),
-                        name: null,
-                        pattern: Var('a').pattern(),
-                      ),
+                      Var('a').pattern().recordField(),
                     ]),
                     [],
                   ).checkIr('ifCase(expr((int, String)), '
@@ -2051,16 +2001,8 @@
               ifCase(
                 expr('X').checkContext('?'),
                 recordPattern([
-                  shared.RecordPatternField(
-                    node: RecordPatternField(),
-                    name: null,
-                    pattern: Var('a').pattern(type: 'int'),
-                  ),
-                  shared.RecordPatternField(
-                    node: RecordPatternField(),
-                    name: null,
-                    pattern: Var('b').pattern(),
-                  ),
+                  Var('a').pattern(type: 'int').recordField(),
+                  Var('b').pattern().recordField(),
                 ]),
                 [],
               ).checkIr('ifCase(expr(X), recordPattern(varPattern(a, '
@@ -2079,16 +2021,8 @@
               ifCase(
                 expr('dynamic').checkContext('?'),
                 recordPattern([
-                  shared.RecordPatternField(
-                    node: RecordPatternField(),
-                    name: 'a',
-                    pattern: Var('a').pattern(type: 'int'),
-                  ),
-                  shared.RecordPatternField(
-                    node: RecordPatternField(),
-                    name: 'b',
-                    pattern: Var('b').pattern(),
-                  ),
+                  Var('a').pattern(type: 'int').recordField('a'),
+                  Var('b').pattern().recordField('b'),
                 ]),
                 [],
               ).checkIr('ifCase(expr(dynamic), recordPattern(varPattern(a, '
@@ -2110,16 +2044,8 @@
               h.run([
                 match(
                   recordPattern([
-                    shared.RecordPatternField(
-                      node: RecordPatternField(),
-                      name: 'a',
-                      pattern: Var('a').pattern(type: 'int'),
-                    ),
-                    shared.RecordPatternField(
-                      node: RecordPatternField(),
-                      name: 'b',
-                      pattern: Var('b').pattern(),
-                    ),
+                    Var('a').pattern(type: 'int').recordField('a'),
+                    Var('b').pattern().recordField('b'),
                   ]),
                   expr('({int a, String b})').checkContext('({int a, ? b})'),
                 ).checkIr('match(expr(({int a, String b})), '
@@ -2136,17 +2062,9 @@
               h.run([
                 (match(
                   recordPattern([
-                    shared.RecordPatternField(
-                      node: RecordPatternField(),
-                      name: 'a',
-                      pattern: Var('a').pattern(type: 'int')
-                        ..errorId = 'VAR(a)',
-                    ),
-                    shared.RecordPatternField(
-                      node: RecordPatternField(),
-                      name: 'b',
-                      pattern: Var('b').pattern(),
-                    ),
+                    (Var('a').pattern(type: 'int')..errorId = 'VAR(a)')
+                        .recordField('a'),
+                    Var('b').pattern().recordField('b'),
                   ])
                     ..errorId = 'PATTERN',
                   expr('({int a})').checkContext('({int a, ? b})'),
@@ -2171,16 +2089,8 @@
                   ifCase(
                     expr('({int a})').checkContext('?'),
                     recordPattern([
-                      shared.RecordPatternField(
-                        node: RecordPatternField(),
-                        name: 'a',
-                        pattern: Var('a').pattern(),
-                      ),
-                      shared.RecordPatternField(
-                        node: RecordPatternField(),
-                        name: 'b',
-                        pattern: Var('b').pattern(),
-                      ),
+                      Var('a').pattern().recordField('a'),
+                      Var('b').pattern().recordField('b'),
                     ]),
                     [],
                   ).checkIr('ifCase(expr(({int a})), '
@@ -2196,11 +2106,7 @@
                   ifCase(
                     expr('({int a, String b})').checkContext('?'),
                     recordPattern([
-                      shared.RecordPatternField(
-                        node: RecordPatternField(),
-                        name: 'a',
-                        pattern: Var('a').pattern(),
-                      ),
+                      Var('a').pattern().recordField('a'),
                     ]),
                     [],
                   ).checkIr('ifCase(expr(({int a, String b})), '
@@ -2218,23 +2124,15 @@
               ifCase(
                 expr('X').checkContext('?'),
                 recordPattern([
-                  shared.RecordPatternField(
-                    node: RecordPatternField(),
-                    name: null,
-                    pattern: Var('a').pattern(type: 'int'),
-                  ),
-                  shared.RecordPatternField(
-                    node: RecordPatternField(),
-                    name: null,
-                    pattern: Var('b').pattern(),
-                  ),
+                  Var('a').pattern(type: 'int').recordField('a'),
+                  Var('b').pattern().recordField('b'),
                 ]),
                 [],
               ).checkIr('ifCase(expr(X), recordPattern(varPattern(a, '
                   'matchedType: Object?, staticType: int), varPattern(b, '
                   'matchedType: Object?, staticType: Object?), '
                   'matchedType: X, requiredType: '
-                  '(Object?, Object?)), true, block(), noop)'),
+                  '({Object? a, Object? b})), true, block(), noop)'),
             ]);
           });
         });
diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
index 5203057..5069192 100644
--- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
+++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
@@ -1802,7 +1802,9 @@
 LintCode.empty_statements:
   status: hasFix
 LintCode.enable_null_safety:
-  status: needsEvaluation
+  status: noFix
+  notes: |-
+    The correction is to run the migration tool.
 LintCode.eol_at_end_of_file:
   status: hasFix
 LintCode.exhaustive_cases:
@@ -1826,7 +1828,7 @@
 LintCode.leading_newlines_in_multiline_strings:
   status: hasFix
 LintCode.library_annotations:
-  status: needsEvaluation
+  status: needsFix
 LintCode.library_names:
   status: needsEvaluation
 LintCode.library_prefixes:
diff --git a/pkg/analyzer/lib/src/error/dead_code_verifier.dart b/pkg/analyzer/lib/src/error/dead_code_verifier.dart
index 318fa4d..19c161b 100644
--- a/pkg/analyzer/lib/src/error/dead_code_verifier.dart
+++ b/pkg/analyzer/lib/src/error/dead_code_verifier.dart
@@ -6,6 +6,7 @@
 import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/nullability_suffix.dart';
 import 'package:analyzer/dart/element/type.dart';
 import 'package:analyzer/error/error.dart';
 import 'package:analyzer/error/listener.dart';
@@ -16,6 +17,7 @@
 import 'package:analyzer/src/dart/resolver/scope.dart';
 import 'package:analyzer/src/error/codes.dart';
 import 'package:analyzer/src/generated/constant.dart';
+import 'package:collection/collection.dart';
 
 typedef _CatchClausesVerifierReporter = void Function(
   CatchClause first,
@@ -581,6 +583,17 @@
     _catchClausesVerifiers.removeLast();
   }
 
+  void verifyCascadeExpression(CascadeExpression node) {
+    var first = node.cascadeSections.firstOrNull;
+    if (first is PropertyAccess) {
+      _verifyUnassignedSimpleIdentifier(node, node.target, first.operator);
+    } else if (first is MethodInvocation) {
+      _verifyUnassignedSimpleIdentifier(node, node.target, first.operator);
+    } else if (first is IndexExpression) {
+      _verifyUnassignedSimpleIdentifier(node, node.target, first.period);
+    }
+  }
+
   void verifyCatchClause(CatchClause node) {
     var verifier = _catchClausesVerifiers.last;
     if (verifier._done) return;
@@ -588,6 +601,18 @@
     verifier.nextCatchClause(node);
   }
 
+  void verifyIndexExpression(IndexExpression node) {
+    _verifyUnassignedSimpleIdentifier(node, node.target, node.question);
+  }
+
+  void verifyMethodInvocation(MethodInvocation node) {
+    _verifyUnassignedSimpleIdentifier(node, node.target, node.operator);
+  }
+
+  void verifyPropertyAccess(PropertyAccess node) {
+    _verifyUnassignedSimpleIdentifier(node, node.target, node.operator);
+  }
+
   void visitNode(AstNode node) {
     // Comments are visited after bodies of functions.
     // So, they look unreachable, but this does not make sense.
@@ -636,6 +661,38 @@
       }
     }
   }
+
+  void _verifyUnassignedSimpleIdentifier(
+      AstNode node, Expression? target, Token? operator) {
+    var flowAnalysis = _flowAnalysis;
+    if (flowAnalysis == null) return;
+
+    var operatorType = operator?.type;
+    if (operatorType != TokenType.QUESTION &&
+        operatorType != TokenType.QUESTION_PERIOD &&
+        operatorType != TokenType.QUESTION_PERIOD_PERIOD) {
+      return;
+    }
+    if (target?.staticType?.nullabilitySuffix != NullabilitySuffix.question) {
+      return;
+    }
+
+    target = target?.unParenthesized;
+    if (target is SimpleIdentifier) {
+      var element = target.staticElement;
+      if (element is PromotableElement &&
+          flowAnalysis.isDefinitelyUnassigned(target, element)) {
+        var parent = node.parent;
+        while (parent is MethodInvocation ||
+            parent is PropertyAccess ||
+            parent is IndexExpression) {
+          node = parent!;
+          parent = node.parent;
+        }
+        _errorReporter.reportErrorForNode(HintCode.DEAD_CODE, node);
+      }
+    }
+  }
 }
 
 /// A visitor that finds a [BreakStatement] for a specified [DoStatement].
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index f820cd7..1a8e0f3 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -1710,6 +1710,7 @@
 
     nullShortingTermination(node);
     _insertImplicitCallReference(node, contextType: contextType);
+    nullSafetyDeadCodeVerifier.verifyCascadeExpression(node);
   }
 
   @override
@@ -2449,6 +2450,7 @@
 
     nullShortingTermination(node);
     _insertImplicitCallReference(replacement, contextType: contextType);
+    nullSafetyDeadCodeVerifier.verifyIndexExpression(node);
   }
 
   @override
@@ -2614,6 +2616,7 @@
     checkForArgumentTypesNotAssignableInList(
         node.argumentList, whyNotPromotedList);
     _insertImplicitCallReference(replacement, contextType: contextType);
+    nullSafetyDeadCodeVerifier.verifyMethodInvocation(node);
   }
 
   @override
@@ -2794,6 +2797,7 @@
 
     nullShortingTermination(node);
     _insertImplicitCallReference(replacement, contextType: contextType);
+    nullSafetyDeadCodeVerifier.verifyPropertyAccess(node);
   }
 
   @override
diff --git a/pkg/analyzer/test/src/dart/resolution/non_nullable_test.dart b/pkg/analyzer/test/src/dart/resolution/non_nullable_test.dart
index 7eb4066..270c475 100644
--- a/pkg/analyzer/test/src/dart/resolution/non_nullable_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/non_nullable_test.dart
@@ -80,8 +80,7 @@
 
   test_local_getterNullAwareAccess_interfaceType() async {
     await assertNoErrorsInCode(r'''
-main() {
-  int? x;
+f(int? x) {
   return x?.isEven;
 }
 ''');
@@ -131,8 +130,7 @@
   bool x() => true;
 }
 
-main() {
-  C? c;
+f(C? c) {
   return c?.x();
 }
 ''');
diff --git a/pkg/analyzer/test/src/diagnostics/dead_code_test.dart b/pkg/analyzer/test/src/diagnostics/dead_code_test.dart
index ed92c67..cb7279a 100644
--- a/pkg/analyzer/test/src/diagnostics/dead_code_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/dead_code_test.dart
@@ -958,6 +958,17 @@
     ]);
   }
 
+  test_assigned_methodInvocation() async {
+    await assertErrorsInCode(r'''
+void f() {
+  int? i = 1;
+  i?.truncate();
+}
+''', [
+      error(StaticWarningCode.INVALID_NULL_AWARE_OPERATOR, 28, 2),
+    ]);
+  }
+
   test_doWhile() async {
     await assertErrorsInCode(r'''
 void f(bool c) {
@@ -1322,6 +1333,26 @@
     ]);
   }
 
+  test_notUnassigned_propertyAccess() async {
+    await assertNoErrorsInCode(r'''
+void f(int? i) {
+  (i)?.sign;
+}
+''');
+  }
+
+  test_potentiallyAssigned_propertyAccess() async {
+    await assertNoErrorsInCode(r'''
+void f(bool b) {
+  int? i;
+  if (b) {
+    i = 1;
+  }
+  (i)?.sign;
+}
+''');
+  }
+
   test_returnTypeNever_function() async {
     await assertErrorsInCode(r'''
 Never foo() => throw 0;
@@ -1393,6 +1424,116 @@
       error(HintCode.DEAD_CODE, 87, 14),
     ]);
   }
+
+  test_unassigned_cascadeExpression_indexExpression() async {
+    await assertErrorsInCode(r'''
+void f() {
+  List<int>? l;
+  l?..[0]..length;
+}
+''', [
+      error(HintCode.DEAD_CODE, 29, 15),
+    ]);
+  }
+
+  test_unassigned_cascadeExpression_methodInvocation() async {
+    await assertErrorsInCode(r'''
+void f() {
+  int? i;
+  i?..toInt()..isEven;
+}
+''', [
+      error(HintCode.DEAD_CODE, 23, 19),
+    ]);
+  }
+
+  test_unassigned_cascadeExpression_propertyAccess() async {
+    await assertErrorsInCode(r'''
+void f() {
+  int? i;
+  i?..sign..isEven;
+}
+''', [
+      error(HintCode.DEAD_CODE, 23, 16),
+    ]);
+  }
+
+  test_unassigned_indexExpression() async {
+    await assertErrorsInCode(r'''
+void f() {
+  List<int>? l;
+  l?[0];
+}
+''', [
+      error(HintCode.DEAD_CODE, 29, 5),
+    ]);
+  }
+
+  test_unassigned_indexExpression_indexExpression() async {
+    await assertErrorsInCode(r'''
+void f() {
+  List<List<int>>? l;
+  l?[0][0];
+}
+''', [
+      error(HintCode.DEAD_CODE, 35, 8),
+    ]);
+  }
+
+  test_unassigned_methodInvocation() async {
+    await assertErrorsInCode(r'''
+void f() {
+  int? i;
+  i?.truncate();
+}
+''', [
+      error(HintCode.DEAD_CODE, 23, 13),
+    ]);
+  }
+
+  test_unassigned_methodInvocation_methodInvocation() async {
+    await assertErrorsInCode(r'''
+void f() {
+  int? i;
+  i?.truncate().truncate();
+}
+''', [
+      error(HintCode.DEAD_CODE, 23, 24),
+    ]);
+  }
+
+  test_unassigned_methodInvocation_propertyAccess() async {
+    await assertErrorsInCode(r'''
+void f() {
+  int? i;
+  i?.truncate().sign;
+}
+''', [
+      error(HintCode.DEAD_CODE, 23, 18),
+    ]);
+  }
+
+  test_unassigned_propertyAccess() async {
+    await assertErrorsInCode(r'''
+void f() {
+  int? i;
+  (i)?.sign;
+}
+''', [
+      error(HintCode.DEAD_CODE, 23, 9),
+    ]);
+  }
+
+  test_unassigned_propertyAccess_propertyAccess() async {
+    await assertErrorsInCode(r'''
+void f() {
+  int? i;
+  (i)?.sign.sign;
+}
+''', [
+      error(HintCode.DEAD_CODE, 23, 14),
+    ]);
+  }
 }
 
 @reflectiveTest
diff --git a/pkg/analyzer/test/src/diagnostics/use_of_nullable_value_test.dart b/pkg/analyzer/test/src/diagnostics/use_of_nullable_value_test.dart
index add8a1e..c5f975b 100644
--- a/pkg/analyzer/test/src/diagnostics/use_of_nullable_value_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/use_of_nullable_value_test.dart
@@ -811,7 +811,7 @@
 
   test_cascade_nonNullable() async {
     await assertNoErrorsInCode(r'''
-m() {
+f() {
   int x = 0;
   x..isEven;
 }
@@ -820,20 +820,18 @@
 
   test_cascade_nullable_indexed_assignment() async {
     await assertErrorsInCode(r'''
-m() {
-  List<int>? x;
+f(List<int>? x) {
   x..[0] = 1;
 }
 ''', [
       error(CompileTimeErrorCode.UNCHECKED_METHOD_INVOCATION_OF_NULLABLE_VALUE,
-          27, 1),
+          23, 1),
     ]);
   }
 
   test_cascade_nullable_indexed_assignment_null_aware() async {
     await assertNoErrorsInCode(r'''
-m() {
-  List<int>? x;
+f(List<int>? x) {
   x?..[0] = 1;
 }
 ''');
@@ -853,8 +851,7 @@
 
   test_cascade_nullable_method_invocation_null_aware() async {
     await assertNoErrorsInCode(r'''
-m() {
-  int? x;
+f(int? x) {
   x?..abs();
 }
 ''');
@@ -862,20 +859,18 @@
 
   test_cascade_nullable_property_access() async {
     await assertErrorsInCode(r'''
-m() {
-  int? x;
+f(int? x) {
   x..isEven;
 }
 ''', [
       error(CompileTimeErrorCode.UNCHECKED_PROPERTY_ACCESS_OF_NULLABLE_VALUE,
-          21, 6),
+          17, 6),
     ]);
   }
 
   test_cascade_nullable_property_access_null_aware() async {
     await assertNoErrorsInCode(r'''
-m() {
-  int? x;
+m(int? x) {
   x?..isEven;
 }
 ''');
@@ -1137,8 +1132,7 @@
 
   test_member_questionDot_nullable() async {
     await assertNoErrorsInCode(r'''
-m() {
-  int? x;
+f(int? x) {
   x?.isEven;
 }
 ''');
@@ -1199,8 +1193,7 @@
 
   test_method_questionDot_nullable() async {
     await assertNoErrorsInCode(r'''
-m() {
-  int? x;
+m(int? x) {
   x?.round();
 }
 ''');
diff --git a/pkg/dartdev/test/commands/compile_test.dart b/pkg/dartdev/test/commands/compile_test.dart
index ec601f4..64dafce 100644
--- a/pkg/dartdev/test/commands/compile_test.dart
+++ b/pkg/dartdev/test/commands/compile_test.dart
@@ -44,8 +44,8 @@
         ],
       );
 
-      expect(result.stderr, contains(soundNullSafetyMessage));
-      expect(result.stdout, isEmpty);
+      expect(result.stdout, contains(soundNullSafetyMessage));
+      expect(result.stderr, isEmpty);
       expect(result.exitCode, 0);
       expect(File(outFile).existsSync(), true,
           reason: 'File not found: $outFile');
@@ -85,8 +85,8 @@
         ],
       );
 
-      expect(result.stderr, contains(soundNullSafetyMessage));
-      expect(result.stdout, isEmpty);
+      expect(result.stdout, contains(soundNullSafetyMessage));
+      expect(result.stderr, isEmpty);
       expect(result.exitCode, 0);
       expect(File(outFile).existsSync(), true,
           reason: 'File not found: $outFile');
diff --git a/pkg/front_end/lib/src/fasta/builder/builder_mixins.dart b/pkg/front_end/lib/src/fasta/builder/builder_mixins.dart
new file mode 100644
index 0000000..954c274
--- /dev/null
+++ b/pkg/front_end/lib/src/fasta/builder/builder_mixins.dart
@@ -0,0 +1,162 @@
+// 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/class_hierarchy.dart';
+
+import '../messages.dart';
+import '../problems.dart';
+import '../scope.dart';
+import '../source/source_library_builder.dart';
+import 'builder.dart';
+import 'declaration_builder.dart';
+import 'field_builder.dart';
+import 'library_builder.dart';
+import 'member_builder.dart';
+import 'nullability_builder.dart';
+import 'type_builder.dart';
+import 'type_variable_builder.dart';
+
+/// Shared implementation between extension and view builders.
+mixin DeclarationBuilderMixin implements DeclarationBuilder {
+  /// Type parameters declared.
+  ///
+  /// This is `null` if the declaration is not generic.
+  List<TypeVariableBuilder>? get typeParameters;
+
+  /// Lookup a static member of this declaration.
+  @override
+  Builder? findStaticBuilder(
+      String name, int charOffset, Uri fileUri, LibraryBuilder accessingLibrary,
+      {bool isSetter = false}) {
+    if (accessingLibrary.nameOriginBuilder.origin !=
+            libraryBuilder.nameOriginBuilder.origin &&
+        name.startsWith("_")) {
+      return null;
+    }
+    Builder? declaration = isSetter
+        ? scope.lookupSetter(name, charOffset, fileUri, isInstanceScope: false)
+        : scope.lookup(name, charOffset, fileUri, isInstanceScope: false);
+    // TODO(johnniwinther): Handle patched extensions/views.
+    return declaration;
+  }
+
+  @override
+  DartType buildAliasedType(
+      LibraryBuilder library,
+      NullabilityBuilder nullabilityBuilder,
+      List<TypeBuilder>? arguments,
+      TypeUse typeUse,
+      Uri fileUri,
+      int charOffset,
+      ClassHierarchyBase? hierarchy,
+      {required bool hasExplicitTypeArguments}) {
+    return buildAliasedTypeWithBuiltArguments(
+        library,
+        nullabilityBuilder.build(library),
+        _buildAliasedTypeArguments(library, arguments, hierarchy),
+        typeUse,
+        fileUri,
+        charOffset,
+        hasExplicitTypeArguments: hasExplicitTypeArguments);
+  }
+
+  @override
+  int get typeVariablesCount => typeParameters?.length ?? 0;
+
+  List<DartType> _buildAliasedTypeArguments(LibraryBuilder library,
+      List<TypeBuilder>? arguments, ClassHierarchyBase? hierarchy) {
+    if (arguments == null && typeParameters == null) {
+      return <DartType>[];
+    }
+
+    if (arguments == null && typeParameters != null) {
+      List<DartType> result =
+          new List<DartType>.generate(typeParameters!.length, (int i) {
+        return typeParameters![i].defaultType!.buildAliased(
+            library, TypeUse.defaultTypeAsTypeArgument, hierarchy);
+      }, growable: true);
+      if (library is SourceLibraryBuilder) {
+        library.inferredTypes.addAll(result);
+      }
+      return result;
+    }
+
+    if (arguments != null && arguments.length != typeVariablesCount) {
+      // That should be caught and reported as a compile-time error earlier.
+      return unhandled(
+          templateTypeArgumentMismatch
+              .withArguments(typeVariablesCount)
+              .problemMessage,
+          "buildTypeArguments",
+          -1,
+          null);
+    }
+
+    assert(arguments!.length == typeVariablesCount);
+    List<DartType> result =
+        new List<DartType>.generate(arguments!.length, (int i) {
+      return arguments[i]
+          .buildAliased(library, TypeUse.typeArgument, hierarchy);
+    }, growable: true);
+    return result;
+  }
+
+  void forEach(void f(String name, Builder builder)) {
+    scope
+        .filteredNameIterator(
+            includeDuplicates: false, includeAugmentations: false)
+        .forEach(f);
+  }
+
+  @override
+  InterfaceType? get thisType => null;
+
+  @override
+  Builder? lookupLocalMember(String name,
+      {bool setter = false, bool required = false}) {
+    // TODO(johnniwinther): Support patching on extensions/views.
+    Builder? builder = scope.lookupLocalMember(name, setter: setter);
+    if (required && builder == null) {
+      internalProblem(
+          templateInternalProblemNotFoundIn.withArguments(
+              name, fullNameForErrors),
+          -1,
+          null);
+    }
+    return builder;
+  }
+
+  Builder? lookupLocalMemberByName(Name name,
+      {bool setter = false, bool required = false}) {
+    Builder? builder =
+        lookupLocalMember(name.text, setter: setter, required: required);
+    if (builder == null && setter) {
+      // When looking up setters, we include assignable fields.
+      builder = lookupLocalMember(name.text, setter: false, required: required);
+      if (builder is! FieldBuilder || !builder.isAssignable) {
+        builder = null;
+      }
+    }
+    if (builder != null) {
+      if (name.isPrivate && libraryBuilder.library != name.library) {
+        builder = null;
+      } else if (builder is FieldBuilder &&
+          !builder.isStatic &&
+          !builder.isExternal) {
+        // Non-external extension instance fields are invalid.
+        builder = null;
+      } else if (builder.isDuplicate) {
+        // Duplicates are not visible in the instance scope.
+        builder = null;
+      } else if (builder is MemberBuilder && builder.isConflictingSetter) {
+        // Conflicting setters are not visible in the instance scope.
+        // TODO(johnniwinther): Should we return an [AmbiguousBuilder] here and
+        // above?
+        builder = null;
+      }
+    }
+    return builder;
+  }
+}
diff --git a/pkg/front_end/lib/src/fasta/builder/declaration_builder.dart b/pkg/front_end/lib/src/fasta/builder/declaration_builder.dart
index cec4048..a0d0db4 100644
--- a/pkg/front_end/lib/src/fasta/builder/declaration_builder.dart
+++ b/pkg/front_end/lib/src/fasta/builder/declaration_builder.dart
@@ -6,7 +6,6 @@
 
 import '../messages.dart';
 import '../scope.dart';
-
 import 'builder.dart';
 import 'library_builder.dart';
 import 'metadata_builder.dart';
diff --git a/pkg/front_end/lib/src/fasta/builder/extension_builder.dart b/pkg/front_end/lib/src/fasta/builder/extension_builder.dart
index f31ee90..f45c315 100644
--- a/pkg/front_end/lib/src/fasta/builder/extension_builder.dart
+++ b/pkg/front_end/lib/src/fasta/builder/extension_builder.dart
@@ -3,20 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:kernel/ast.dart';
-import 'package:kernel/class_hierarchy.dart';
-
-import '../fasta_codes.dart'
-    show templateInternalProblemNotFoundIn, templateTypeArgumentMismatch;
-import '../problems.dart';
 import '../scope.dart';
 import '../source/source_library_builder.dart';
 import 'builder.dart';
+import 'builder_mixins.dart';
 import 'declaration_builder.dart';
-import 'field_builder.dart';
 import 'library_builder.dart';
-import 'member_builder.dart';
 import 'metadata_builder.dart';
-import 'nullability_builder.dart';
 import 'type_builder.dart';
 import 'type_variable_builder.dart';
 
@@ -50,54 +43,12 @@
 }
 
 abstract class ExtensionBuilderImpl extends DeclarationBuilderImpl
+    with DeclarationBuilderMixin
     implements ExtensionBuilder {
   ExtensionBuilderImpl(List<MetadataBuilder>? metadata, int modifiers,
       String name, LibraryBuilder parent, int charOffset, Scope scope)
       : super(metadata, modifiers, name, parent, charOffset, scope);
 
-  /// Lookup a static member of this declaration.
-  @override
-  Builder? findStaticBuilder(
-      String name, int charOffset, Uri fileUri, LibraryBuilder accessingLibrary,
-      {bool isSetter = false}) {
-    if (accessingLibrary.nameOriginBuilder.origin !=
-            libraryBuilder.nameOriginBuilder.origin &&
-        name.startsWith("_")) {
-      return null;
-    }
-    Builder? declaration = isSetter
-        ? scope.lookupSetter(name, charOffset, fileUri, isInstanceScope: false)
-        : scope.lookup(name, charOffset, fileUri, isInstanceScope: false);
-    // TODO(johnniwinther): Handle patched extensions.
-    return declaration;
-  }
-
-  @override
-  DartType buildAliasedType(
-      LibraryBuilder library,
-      NullabilityBuilder nullabilityBuilder,
-      List<TypeBuilder>? arguments,
-      TypeUse typeUse,
-      Uri fileUri,
-      int charOffset,
-      ClassHierarchyBase? hierarchy,
-      {required bool hasExplicitTypeArguments}) {
-    if (library is SourceLibraryBuilder &&
-        library.libraryFeatures.extensionTypes.isEnabled) {
-      return buildAliasedTypeWithBuiltArguments(
-          library,
-          nullabilityBuilder.build(library),
-          _buildAliasedTypeArguments(library, arguments, hierarchy),
-          typeUse,
-          fileUri,
-          charOffset,
-          hasExplicitTypeArguments: hasExplicitTypeArguments);
-    } else {
-      throw new UnsupportedError("ExtensionBuilder.buildType is not supported"
-          "in library '${library.importUri}'.");
-    }
-  }
-
   @override
   DartType buildAliasedTypeWithBuiltArguments(
       LibraryBuilder library,
@@ -118,108 +69,8 @@
   }
 
   @override
-  int get typeVariablesCount => typeParameters?.length ?? 0;
-
-  List<DartType> _buildAliasedTypeArguments(LibraryBuilder library,
-      List<TypeBuilder>? arguments, ClassHierarchyBase? hierarchy) {
-    if (arguments == null && typeParameters == null) {
-      return <DartType>[];
-    }
-
-    if (arguments == null && typeParameters != null) {
-      List<DartType> result =
-          new List<DartType>.generate(typeParameters!.length, (int i) {
-        return typeParameters![i].defaultType!.buildAliased(
-            library, TypeUse.defaultTypeAsTypeArgument, hierarchy);
-      }, growable: true);
-      if (library is SourceLibraryBuilder) {
-        library.inferredTypes.addAll(result);
-      }
-      return result;
-    }
-
-    if (arguments != null && arguments.length != typeVariablesCount) {
-      // That should be caught and reported as a compile-time error earlier.
-      return unhandled(
-          templateTypeArgumentMismatch
-              .withArguments(typeVariablesCount)
-              .problemMessage,
-          "buildTypeArguments",
-          -1,
-          null);
-    }
-
-    assert(arguments!.length == typeVariablesCount);
-    List<DartType> result =
-        new List<DartType>.generate(arguments!.length, (int i) {
-      return arguments[i]
-          .buildAliased(library, TypeUse.typeArgument, hierarchy);
-    }, growable: true);
-    return result;
-  }
-
-  @override
-  void forEach(void f(String name, Builder builder)) {
-    scope
-        .filteredNameIterator(
-            includeDuplicates: false, includeAugmentations: false)
-        .forEach(f);
-  }
-
-  @override
   bool get isExtension => true;
 
   @override
-  InterfaceType? get thisType => null;
-
-  @override
-  Builder? lookupLocalMember(String name,
-      {bool setter = false, bool required = false}) {
-    // TODO(johnniwinther): Support patching on extensions.
-    Builder? builder = scope.lookupLocalMember(name, setter: setter);
-    if (required && builder == null) {
-      internalProblem(
-          templateInternalProblemNotFoundIn.withArguments(
-              name, fullNameForErrors),
-          -1,
-          null);
-    }
-    return builder;
-  }
-
-  @override
-  Builder? lookupLocalMemberByName(Name name,
-      {bool setter = false, bool required = false}) {
-    Builder? builder =
-        lookupLocalMember(name.text, setter: setter, required: required);
-    if (builder == null && setter) {
-      // When looking up setters, we include assignable fields.
-      builder = lookupLocalMember(name.text, setter: false, required: required);
-      if (builder is! FieldBuilder || !builder.isAssignable) {
-        builder = null;
-      }
-    }
-    if (builder != null) {
-      if (name.isPrivate && libraryBuilder.library != name.library) {
-        builder = null;
-      } else if (builder is FieldBuilder &&
-          !builder.isStatic &&
-          !builder.isExternal) {
-        // Non-external extension instance fields are invalid.
-        builder = null;
-      } else if (builder.isDuplicate) {
-        // Duplicates are not visible in the instance scope.
-        builder = null;
-      } else if (builder is MemberBuilder && builder.isConflictingSetter) {
-        // Conflicting setters are not visible in the instance scope.
-        // TODO(johnniwinther): Should we return an [AmbiguousBuilder] here and
-        // above?
-        builder = null;
-      }
-    }
-    return builder;
-  }
-
-  @override
   String get debugName => "ExtensionBuilder";
 }
diff --git a/pkg/front_end/lib/src/fasta/builder/view_builder.dart b/pkg/front_end/lib/src/fasta/builder/view_builder.dart
new file mode 100644
index 0000000..b20df5d
--- /dev/null
+++ b/pkg/front_end/lib/src/fasta/builder/view_builder.dart
@@ -0,0 +1,66 @@
+// 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 '../scope.dart';
+import 'builder.dart';
+import 'builder_mixins.dart';
+import 'declaration_builder.dart';
+import 'library_builder.dart';
+import 'metadata_builder.dart';
+import 'type_builder.dart';
+import 'type_variable_builder.dart';
+
+abstract class ViewBuilder implements DeclarationBuilder {
+  /// Type parameters declared on the view.
+  ///
+  /// This is `null` if the view is not generic.
+  List<TypeVariableBuilder>? get typeParameters;
+
+  /// The type of the underlying representation.
+  DartType get representationType;
+
+  /// Return the [View] built by this builder.
+  View get view;
+
+  /// Looks up extension member by [name] taking privacy into account.
+  ///
+  /// If [setter] is `true` the sought member is a setter or assignable field.
+  /// If [required] is `true` and no member is found an internal problem is
+  /// reported.
+  ///
+  /// If the extension member is a duplicate, `null` is returned.
+  // TODO(johnniwinther): Support [AmbiguousBuilder] here and in instance
+  // member lookup to avoid reporting that the member doesn't exist when it is
+  // duplicate.
+  Builder? lookupLocalMemberByName(Name name,
+      {bool setter = false, bool required = false});
+
+  /// Calls [f] for each member declared in this extension.
+  void forEach(void f(String name, Builder builder));
+}
+
+abstract class ViewBuilderImpl extends DeclarationBuilderImpl
+    with DeclarationBuilderMixin
+    implements ViewBuilder {
+  ViewBuilderImpl(List<MetadataBuilder>? metadata, int modifiers, String name,
+      LibraryBuilder parent, int charOffset, Scope scope)
+      : super(metadata, modifiers, name, parent, charOffset, scope);
+
+  @override
+  DartType buildAliasedTypeWithBuiltArguments(
+      LibraryBuilder library,
+      Nullability nullability,
+      List<DartType> arguments,
+      TypeUse typeUse,
+      Uri fileUri,
+      int charOffset,
+      {required bool hasExplicitTypeArguments}) {
+    return new ViewType(view, nullability, arguments);
+  }
+
+  @override
+  String get debugName => "ViewBuilder";
+}
diff --git a/pkg/front_end/lib/src/fasta/kernel/type_algorithms.dart b/pkg/front_end/lib/src/fasta/kernel/type_algorithms.dart
index 76dd95c..a674477 100644
--- a/pkg/front_end/lib/src/fasta/kernel/type_algorithms.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/type_algorithms.dart
@@ -24,6 +24,7 @@
 import '../builder/type_declaration_builder.dart';
 import '../builder/type_variable_builder.dart';
 
+import '../builder/view_builder.dart';
 import '../dill/dill_class_builder.dart' show DillClassBuilder;
 
 import '../dill/dill_type_alias_builder.dart' show DillTypeAliasBuilder;
@@ -45,6 +46,7 @@
 import '../source/source_class_builder.dart';
 import '../source/source_extension_builder.dart';
 import '../source/source_type_alias_builder.dart';
+import '../source/source_view_builder.dart';
 
 /// Initial value for "variance" that is to be computed by the compiler.
 const int pendingVariance = -1;
@@ -841,6 +843,8 @@
           }
         } else if (declaration is ExtensionBuilder) {
           visitTypeVariables(declaration.typeParameters);
+        } else if (declaration is ViewBuilder) {
+          visitTypeVariables(declaration.typeParameters);
         } else if (declaration is TypeVariableBuilder) {
           // Do nothing. The type variable is handled by its parent declaration.
         } else if (declaration is BuiltinTypeDeclarationBuilder) {
@@ -932,6 +936,9 @@
   } else if (declaration is SourceExtensionBuilder) {
     return _findRawTypeCyclesFromTypeVariables(
         declaration, declaration.typeParameters);
+  } else if (declaration is SourceViewBuilder) {
+    return _findRawTypeCyclesFromTypeVariables(
+        declaration, declaration.typeParameters);
   } else {
     unhandled('$declaration (${declaration.runtimeType})', 'findRawTypeCycles',
         declaration.charOffset, declaration.fileUri);
@@ -1017,6 +1024,8 @@
     issues.addAll(getInboundReferenceIssues(declaration.typeVariables));
   } else if (declaration is SourceExtensionBuilder) {
     issues.addAll(getInboundReferenceIssues(declaration.typeParameters));
+  } else if (declaration is SourceViewBuilder) {
+    issues.addAll(getInboundReferenceIssues(declaration.typeParameters));
   } else {
     unhandled(
         '$declaration (${declaration.runtimeType})',
diff --git a/pkg/front_end/lib/src/fasta/source/outline_builder.dart b/pkg/front_end/lib/src/fasta/source/outline_builder.dart
index e812158..1de5a1b 100644
--- a/pkg/front_end/lib/src/fasta/source/outline_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/outline_builder.dart
@@ -876,11 +876,6 @@
     List<TypeVariableBuilder>? typeVariables =
         pop() as List<TypeVariableBuilder>?;
     push(typeVariables ?? NullValue.TypeVariables);
-    libraryBuilder.currentTypeParameterScopeBuilder
-        .markAsClassDeclaration(name.lexeme, name.charOffset, typeVariables);
-    libraryBuilder.setCurrentClassName(name.lexeme);
-    inAbstractClass = abstractToken != null;
-    push(abstractToken != null ? abstractMask : 0);
     if (macroToken != null) {
       if (reportIfNotEnabled(
           libraryFeatures.macros, macroToken.charOffset, macroToken.length)) {
@@ -899,6 +894,16 @@
         sealedToken = null;
       }
     }
+    if (viewToken != null) {
+      libraryBuilder.currentTypeParameterScopeBuilder
+          .markAsViewDeclaration(name.lexeme, name.charOffset, typeVariables);
+    } else {
+      libraryBuilder.currentTypeParameterScopeBuilder
+          .markAsClassDeclaration(name.lexeme, name.charOffset, typeVariables);
+    }
+    libraryBuilder.setCurrentClassName(name.lexeme);
+    inAbstractClass = abstractToken != null;
+    push(abstractToken != null ? abstractMask : 0);
     push(macroToken ?? NullValue.Token);
     push(viewToken ?? NullValue.Token);
     push(sealedToken ?? NullValue.Token);
@@ -1243,20 +1248,37 @@
         }
       }
 
-      libraryBuilder.addClass(
+      if (viewToken != null) {
+        libraryBuilder.addViewDeclaration(
           metadata,
           modifiers,
           name as String,
           typeVariables,
-          supertype,
-          mixinApplication,
-          interfaces,
+          /*supertype,
+            mixinApplication,
+            interfaces,*/
           startCharOffset,
           nameOffset,
           endToken.charOffset,
-          supertypeOffset,
-          isMacro: macroToken != null,
-          isAugmentation: augmentToken != null);
+          /*supertypeOffset,
+            isAugmentation: augmentToken != null*/
+        );
+      } else {
+        libraryBuilder.addClass(
+            metadata,
+            modifiers,
+            name as String,
+            typeVariables,
+            supertype,
+            mixinApplication,
+            interfaces,
+            startCharOffset,
+            nameOffset,
+            endToken.charOffset,
+            supertypeOffset,
+            isMacro: macroToken != null,
+            isAugmentation: augmentToken != null);
+      }
     }
     libraryBuilder.setCurrentClassName(null);
     popDeclarationContext(DeclarationContext.Class);
diff --git a/pkg/front_end/lib/src/fasta/source/source_builder_mixins.dart b/pkg/front_end/lib/src/fasta/source/source_builder_mixins.dart
new file mode 100644
index 0000000..6563dd0
--- /dev/null
+++ b/pkg/front_end/lib/src/fasta/source/source_builder_mixins.dart
@@ -0,0 +1,179 @@
+// Copyright (c) 2019, 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/class_hierarchy.dart';
+import 'package:kernel/type_environment.dart';
+
+import '../builder/builder.dart';
+import '../builder/builder_mixins.dart';
+import '../builder/class_builder.dart';
+import '../builder/declaration_builder.dart';
+import '../builder/library_builder.dart';
+import '../builder/metadata_builder.dart';
+import '../builder/procedure_builder.dart';
+import '../fasta_codes.dart'
+    show templateExtensionMemberConflictsWithObjectMember;
+import '../kernel/kernel_helper.dart';
+import '../problems.dart';
+import '../scope.dart';
+import '../util/helpers.dart';
+import 'source_field_builder.dart';
+import 'source_library_builder.dart';
+import 'source_member_builder.dart';
+import 'source_procedure_builder.dart';
+
+mixin SourceDeclarationBuilderMixin
+    implements DeclarationBuilder, DeclarationBuilderMixin {
+  @override
+  SourceLibraryBuilder get libraryBuilder;
+
+  @override
+  Uri get fileUri;
+
+  /// Returns the [Annotatable] node that holds the annotations declared on
+  /// this declaration or its augmentations.
+  Annotatable get annotatable;
+
+  /// Builds the [Extension] for this extension build and inserts the members
+  /// into the [Library] of [libraryBuilder].
+  ///
+  /// [addMembersToLibrary] is `true` if the extension members should be added
+  /// to the library. This is `false` if the extension is in conflict with
+  /// another library member. In this case, the extension member should not be
+  /// added to the library to avoid name clashes with other members in the
+  /// library.
+  void buildInternal(LibraryBuilder coreLibrary,
+      {required bool addMembersToLibrary}) {
+    SourceLibraryBuilder.checkMemberConflicts(libraryBuilder, scope,
+        checkForInstanceVsStaticConflict: true,
+        checkForMethodVsSetterConflict: true);
+
+    ClassBuilder objectClassBuilder =
+        coreLibrary.lookupLocalMember('Object', required: true) as ClassBuilder;
+
+    void buildBuilders(String name, Builder declaration) {
+      Builder? objectGetter = objectClassBuilder.lookupLocalMember(name);
+      Builder? objectSetter =
+          objectClassBuilder.lookupLocalMember(name, setter: true);
+      if (objectGetter != null && !objectGetter.isStatic ||
+          objectSetter != null && !objectSetter.isStatic) {
+        addProblem(
+            // TODO(johnniwinther): Use a different error message for views.
+            templateExtensionMemberConflictsWithObjectMember
+                .withArguments(name),
+            declaration.charOffset,
+            name.length);
+      }
+      if (declaration.parent != this) {
+        if (fileUri != declaration.parent!.fileUri) {
+          unexpected("$fileUri", "${declaration.parent!.fileUri}", charOffset,
+              fileUri);
+        } else {
+          unexpected(fullNameForErrors, declaration.parent!.fullNameForErrors,
+              charOffset, fileUri);
+        }
+      } else if (declaration is SourceMemberBuilder) {
+        SourceMemberBuilder memberBuilder = declaration;
+        memberBuilder
+            .buildOutlineNodes((Member member, BuiltMemberKind memberKind) {
+          _buildMember(memberBuilder, member, memberKind,
+              addMembersToLibrary: addMembersToLibrary);
+        });
+      } else {
+        unhandled("${declaration.runtimeType}", "buildBuilders",
+            declaration.charOffset, declaration.fileUri);
+      }
+    }
+
+    scope.unfilteredNameIterator.forEach(buildBuilders);
+  }
+
+  int buildBodyNodes({required bool addMembersToLibrary}) {
+    int count = 0;
+    Iterator<SourceMemberBuilder> iterator =
+        scope.filteredIterator<SourceMemberBuilder>(
+            parent: this, includeDuplicates: false, includeAugmentations: true);
+    while (iterator.moveNext()) {
+      SourceMemberBuilder declaration = iterator.current;
+      count +=
+          declaration.buildBodyNodes((Member member, BuiltMemberKind kind) {
+        _buildMember(declaration, member, kind,
+            addMembersToLibrary: addMembersToLibrary);
+      });
+    }
+    return count;
+  }
+
+  void checkTypesInOutline(TypeEnvironment typeEnvironment) {
+    forEach((String name, Builder builder) {
+      if (builder is SourceFieldBuilder) {
+        // Check fields.
+        libraryBuilder.checkTypesInField(builder, typeEnvironment);
+      } else if (builder is SourceProcedureBuilder) {
+        // Check procedures
+        libraryBuilder.checkTypesInFunctionBuilder(builder, typeEnvironment);
+        if (builder.isGetter) {
+          Builder? setterDeclaration =
+              scope.lookupLocalMember(builder.name, setter: true);
+          if (setterDeclaration != null) {
+            libraryBuilder.checkGetterSetterTypes(builder,
+                setterDeclaration as ProcedureBuilder, typeEnvironment);
+          }
+        }
+      } else {
+        assert(false, "Unexpected member: $builder.");
+      }
+    });
+  }
+
+  void buildOutlineExpressions(
+      ClassHierarchy classHierarchy,
+      List<DelayedActionPerformer> delayedActionPerformers,
+      List<DelayedDefaultValueCloner> delayedDefaultValueCloners) {
+    MetadataBuilder.buildAnnotations(annotatable, metadata, libraryBuilder,
+        this, null, fileUri, libraryBuilder.scope);
+    if (typeParameters != null) {
+      for (int i = 0; i < typeParameters!.length; i++) {
+        typeParameters![i].buildOutlineExpressions(libraryBuilder, this, null,
+            classHierarchy, delayedActionPerformers, scope.parent!);
+      }
+    }
+
+    Iterator<SourceMemberBuilder> iterator =
+        scope.filteredIterator<SourceMemberBuilder>(
+            parent: this, includeDuplicates: false, includeAugmentations: true);
+    while (iterator.moveNext()) {
+      iterator.current.buildOutlineExpressions(
+          classHierarchy, delayedActionPerformers, delayedDefaultValueCloners);
+    }
+  }
+
+  void _buildMember(SourceMemberBuilder memberBuilder, Member member,
+      BuiltMemberKind memberKind,
+      {required bool addMembersToLibrary}) {
+    if (addMembersToLibrary &&
+        !memberBuilder.isPatch &&
+        !memberBuilder.isDuplicate &&
+        !memberBuilder.isConflictingSetter) {
+      Reference memberReference;
+      if (member is Field) {
+        libraryBuilder.library.addField(member);
+        memberReference = member.fieldReference;
+      } else if (member is Procedure) {
+        libraryBuilder.library.addProcedure(member);
+        memberReference = member.reference;
+      } else {
+        unhandled("${member.runtimeType}", "buildBuilders", member.fileOffset,
+            member.fileUri);
+      }
+      addMemberDescriptorInternal(
+          memberBuilder, member, memberKind, memberReference);
+    }
+  }
+
+  /// Adds a descriptor for [member] to this declaration.
+  void addMemberDescriptorInternal(SourceMemberBuilder memberBuilder,
+      Member member, BuiltMemberKind memberKind, Reference memberReference);
+}
diff --git a/pkg/front_end/lib/src/fasta/source/source_extension_builder.dart b/pkg/front_end/lib/src/fasta/source/source_extension_builder.dart
index 8faedba..059e1cc 100644
--- a/pkg/front_end/lib/src/fasta/source/source_extension_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_extension_builder.dart
@@ -3,38 +3,31 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:kernel/ast.dart';
-import 'package:kernel/class_hierarchy.dart';
-import 'package:kernel/type_environment.dart';
 
 import '../../base/common.dart';
 import '../builder/builder.dart';
-import '../builder/class_builder.dart';
 import '../builder/extension_builder.dart';
 import '../builder/library_builder.dart';
 import '../builder/metadata_builder.dart';
-import '../builder/procedure_builder.dart';
 import '../builder/type_builder.dart';
 import '../builder/type_variable_builder.dart';
 import '../fasta_codes.dart'
     show
         messagePatchDeclarationMismatch,
         messagePatchDeclarationOrigin,
-        noLength,
-        templateExtensionMemberConflictsWithObjectMember;
-import '../kernel/kernel_helper.dart';
+        noLength;
 import '../operator.dart';
 import '../problems.dart';
 import '../scope.dart';
-import '../util/helpers.dart';
 import 'name_scheme.dart';
-import 'source_field_builder.dart';
+import 'source_builder_mixins.dart';
 import 'source_library_builder.dart';
 import 'source_member_builder.dart';
-import 'source_procedure_builder.dart';
 
 const String extensionThisName = '#this';
 
-class SourceExtensionBuilder extends ExtensionBuilderImpl {
+class SourceExtensionBuilder extends ExtensionBuilderImpl
+    with SourceDeclarationBuilderMixin {
   final Extension _extension;
 
   SourceExtensionBuilder? _origin;
@@ -80,12 +73,12 @@
     extensionName.attachExtension(_extension);
   }
 
-  bool get isUnnamedExtension => extensionName.isUnnamedExtension;
-
   @override
   SourceLibraryBuilder get libraryBuilder =>
       super.libraryBuilder as SourceLibraryBuilder;
 
+  bool get isUnnamedExtension => extensionName.isUnnamedExtension;
+
   @override
   SourceExtensionBuilder get origin => _origin ?? this;
 
@@ -97,6 +90,9 @@
   @override
   Extension get extension => isPatch ? origin._extension : _extension;
 
+  @override
+  Annotatable get annotatable => extension;
+
   /// Builds the [Extension] for this extension build and inserts the members
   /// into the [Library] of [libraryBuilder].
   ///
@@ -111,108 +107,52 @@
     extensionTypeShowHideClauseBuilder.buildAndStoreTypes(
         _extension, libraryBuilder);
 
-    SourceLibraryBuilder.checkMemberConflicts(libraryBuilder, scope,
-        checkForInstanceVsStaticConflict: true,
-        checkForMethodVsSetterConflict: true);
-
-    ClassBuilder objectClassBuilder =
-        coreLibrary.lookupLocalMember('Object', required: true) as ClassBuilder;
-
-    void buildBuilders(String name, Builder declaration) {
-      Builder? objectGetter = objectClassBuilder.lookupLocalMember(name);
-      Builder? objectSetter =
-          objectClassBuilder.lookupLocalMember(name, setter: true);
-      if (objectGetter != null && !objectGetter.isStatic ||
-          objectSetter != null && !objectSetter.isStatic) {
-        addProblem(
-            templateExtensionMemberConflictsWithObjectMember
-                .withArguments(name),
-            declaration.charOffset,
-            name.length);
-      }
-      if (declaration.parent != this) {
-        if (fileUri != declaration.parent!.fileUri) {
-          unexpected("$fileUri", "${declaration.parent!.fileUri}", charOffset,
-              fileUri);
-        } else {
-          unexpected(fullNameForErrors, declaration.parent!.fullNameForErrors,
-              charOffset, fileUri);
-        }
-      } else if (declaration is SourceMemberBuilder) {
-        SourceMemberBuilder memberBuilder = declaration;
-        memberBuilder
-            .buildOutlineNodes((Member member, BuiltMemberKind memberKind) {
-          _buildMember(memberBuilder, member, memberKind,
-              addMembersToLibrary: addMembersToLibrary);
-        });
-      } else {
-        unhandled("${declaration.runtimeType}", "buildBuilders",
-            declaration.charOffset, declaration.fileUri);
-      }
-    }
-
-    scope.unfilteredNameIterator.forEach(buildBuilders);
+    buildInternal(coreLibrary, addMembersToLibrary: addMembersToLibrary);
 
     return _extension;
   }
 
-  void _buildMember(SourceMemberBuilder memberBuilder, Member member,
-      BuiltMemberKind memberKind,
-      {required bool addMembersToLibrary}) {
-    if (addMembersToLibrary &&
-        !memberBuilder.isPatch &&
-        !memberBuilder.isDuplicate &&
-        !memberBuilder.isConflictingSetter) {
-      ExtensionMemberKind kind;
-      String name = memberBuilder.name;
-      switch (memberKind) {
-        case BuiltMemberKind.Constructor:
-        case BuiltMemberKind.RedirectingFactory:
-        case BuiltMemberKind.Field:
-        case BuiltMemberKind.Method:
-          unhandled("${member.runtimeType}:${memberKind}", "buildMembers",
-              memberBuilder.charOffset, memberBuilder.fileUri);
-        case BuiltMemberKind.ExtensionField:
-        case BuiltMemberKind.LateIsSetField:
-          kind = ExtensionMemberKind.Field;
-          break;
-        case BuiltMemberKind.ExtensionMethod:
-          kind = ExtensionMemberKind.Method;
-          break;
-        case BuiltMemberKind.ExtensionGetter:
-        case BuiltMemberKind.LateGetter:
-          kind = ExtensionMemberKind.Getter;
-          break;
-        case BuiltMemberKind.ExtensionSetter:
-        case BuiltMemberKind.LateSetter:
-          kind = ExtensionMemberKind.Setter;
-          break;
-        case BuiltMemberKind.ExtensionOperator:
-          kind = ExtensionMemberKind.Operator;
-          break;
-        case BuiltMemberKind.ExtensionTearOff:
-          kind = ExtensionMemberKind.TearOff;
-          break;
-      }
-      // ignore: unnecessary_null_comparison
-      assert(kind != null);
-      Reference memberReference;
-      if (member is Field) {
-        libraryBuilder.library.addField(member);
-        memberReference = member.fieldReference;
-      } else if (member is Procedure) {
-        libraryBuilder.library.addProcedure(member);
-        memberReference = member.reference;
-      } else {
-        unhandled("${member.runtimeType}", "buildBuilders", member.fileOffset,
-            member.fileUri);
-      }
-      extension.members.add(new ExtensionMemberDescriptor(
-          name: new Name(name, libraryBuilder.library),
-          member: memberReference,
-          isStatic: memberBuilder.isStatic,
-          kind: kind));
+  @override
+  void addMemberDescriptorInternal(SourceMemberBuilder memberBuilder,
+      Member member, BuiltMemberKind memberKind, Reference memberReference) {
+    String name = memberBuilder.name;
+    ExtensionMemberKind kind;
+    switch (memberKind) {
+      case BuiltMemberKind.Constructor:
+      case BuiltMemberKind.RedirectingFactory:
+      case BuiltMemberKind.Field:
+      case BuiltMemberKind.Method:
+        unhandled("${member.runtimeType}:${memberKind}", "buildMembers",
+            memberBuilder.charOffset, memberBuilder.fileUri);
+      case BuiltMemberKind.ExtensionField:
+      case BuiltMemberKind.LateIsSetField:
+        kind = ExtensionMemberKind.Field;
+        break;
+      case BuiltMemberKind.ExtensionMethod:
+        kind = ExtensionMemberKind.Method;
+        break;
+      case BuiltMemberKind.ExtensionGetter:
+      case BuiltMemberKind.LateGetter:
+        kind = ExtensionMemberKind.Getter;
+        break;
+      case BuiltMemberKind.ExtensionSetter:
+      case BuiltMemberKind.LateSetter:
+        kind = ExtensionMemberKind.Setter;
+        break;
+      case BuiltMemberKind.ExtensionOperator:
+        kind = ExtensionMemberKind.Operator;
+        break;
+      case BuiltMemberKind.ExtensionTearOff:
+        kind = ExtensionMemberKind.TearOff;
+        break;
     }
+    // ignore: unnecessary_null_comparison
+    assert(kind != null);
+    extension.members.add(new ExtensionMemberDescriptor(
+        name: new Name(name, libraryBuilder.library),
+        member: memberReference,
+        isStatic: memberBuilder.isStatic,
+        kind: kind));
   }
 
   @override
@@ -222,6 +162,9 @@
       if (retainDataForTesting) {
         patchForTesting = patch;
       }
+      // TODO(johnniwinther): Check that type parameters and on-type match
+      // with origin declaration.
+
       scope.forEachLocalMember((String name, Builder member) {
         Builder? memberPatch =
             patch.scope.lookupLocalMember(name, setter: false);
@@ -236,9 +179,6 @@
           member.applyPatch(memberPatch);
         }
       });
-
-      // TODO(johnniwinther): Check that type parameters and on-type match
-      // with origin declaration.
     } else {
       libraryBuilder.addProblem(messagePatchDeclarationMismatch,
           patch.charOffset, noLength, patch.fileUri, context: [
@@ -247,66 +187,6 @@
       ]);
     }
   }
-
-  int buildBodyNodes({required bool addMembersToLibrary}) {
-    int count = 0;
-    Iterator<SourceMemberBuilder> iterator =
-        scope.filteredIterator<SourceMemberBuilder>(
-            parent: this, includeDuplicates: false, includeAugmentations: true);
-    while (iterator.moveNext()) {
-      SourceMemberBuilder declaration = iterator.current;
-      count +=
-          declaration.buildBodyNodes((Member member, BuiltMemberKind kind) {
-        _buildMember(declaration, member, kind,
-            addMembersToLibrary: addMembersToLibrary);
-      });
-    }
-    return count;
-  }
-
-  void checkTypesInOutline(TypeEnvironment typeEnvironment) {
-    forEach((String name, Builder builder) {
-      if (builder is SourceFieldBuilder) {
-        // Check fields.
-        libraryBuilder.checkTypesInField(builder, typeEnvironment);
-      } else if (builder is SourceProcedureBuilder) {
-        // Check procedures
-        libraryBuilder.checkTypesInFunctionBuilder(builder, typeEnvironment);
-        if (builder.isGetter) {
-          Builder? setterDeclaration =
-              scope.lookupLocalMember(builder.name, setter: true);
-          if (setterDeclaration != null) {
-            libraryBuilder.checkGetterSetterTypes(builder,
-                setterDeclaration as ProcedureBuilder, typeEnvironment);
-          }
-        }
-      } else {
-        assert(false, "Unexpected member: $builder.");
-      }
-    });
-  }
-
-  void buildOutlineExpressions(
-      ClassHierarchy classHierarchy,
-      List<DelayedActionPerformer> delayedActionPerformers,
-      List<DelayedDefaultValueCloner> delayedDefaultValueCloners) {
-    MetadataBuilder.buildAnnotations(isPatch ? origin.extension : extension,
-        metadata, libraryBuilder, this, null, fileUri, libraryBuilder.scope);
-    if (typeParameters != null) {
-      for (int i = 0; i < typeParameters!.length; i++) {
-        typeParameters![i].buildOutlineExpressions(libraryBuilder, this, null,
-            classHierarchy, delayedActionPerformers, scope.parent!);
-      }
-    }
-
-    Iterator<SourceMemberBuilder> iterator =
-        scope.filteredIterator<SourceMemberBuilder>(
-            parent: this, includeDuplicates: false, includeAugmentations: true);
-    while (iterator.moveNext()) {
-      iterator.current.buildOutlineExpressions(
-          classHierarchy, delayedActionPerformers, delayedDefaultValueCloners);
-    }
-  }
 }
 
 class ExtensionTypeShowHideClauseBuilder {
diff --git a/pkg/front_end/lib/src/fasta/source/source_library_builder.dart b/pkg/front_end/lib/src/fasta/source/source_library_builder.dart
index 6c7ccff..31c3b72 100644
--- a/pkg/front_end/lib/src/fasta/source/source_library_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_library_builder.dart
@@ -55,6 +55,7 @@
 import '../builder/type_builder.dart';
 import '../builder/type_declaration_builder.dart';
 import '../builder/type_variable_builder.dart';
+import '../builder/view_builder.dart';
 import '../builder/void_type_declaration_builder.dart';
 import '../combinator.dart' show CombinatorBuilder;
 import '../configuration.dart' show Configuration;
@@ -117,6 +118,7 @@
 import 'source_member_builder.dart';
 import 'source_procedure_builder.dart';
 import 'source_type_alias_builder.dart';
+import 'source_view_builder.dart';
 
 class SourceLibraryBuilder extends LibraryBuilderImpl {
   static const String MALFORMED_URI_SCHEME = "org-dartlang-malformed-uri";
@@ -2160,6 +2162,77 @@
         getterReference: referenceFrom?.reference);
   }
 
+  void addViewDeclaration(
+      List<MetadataBuilder>? metadata,
+      int modifiers,
+      String name,
+      List<TypeVariableBuilder>? typeVariables,
+      int startOffset,
+      int nameOffset,
+      int endOffset) {
+    // Nested declaration began in `OutlineBuilder.beginExtensionDeclaration`.
+    TypeParameterScopeBuilder declaration =
+        endNestedDeclaration(TypeParameterScopeKind.viewDeclaration, name)
+          ..resolveNamedTypes(typeVariables, this);
+    assert(declaration.parent == _libraryTypeParameterScopeBuilder);
+    Map<String, Builder> members = declaration.members!;
+    Map<String, MemberBuilder> constructors = declaration.constructors!;
+    Map<String, MemberBuilder> setters = declaration.setters!;
+
+    Scope classScope = new Scope(
+        local: members,
+        setters: setters,
+        parent: scope.withTypeVariables(typeVariables),
+        debugName: "extension $name",
+        isModifiable: false);
+
+    Extension? referenceFrom = referencesFromIndexed?.lookupExtension(name);
+
+    ViewBuilder viewBuilder = new SourceViewBuilder(
+        metadata,
+        modifiers,
+        declaration.name,
+        typeVariables,
+        classScope,
+        this,
+        startOffset,
+        nameOffset,
+        endOffset,
+        referenceFrom);
+    constructorReferences.clear();
+    Map<String, TypeVariableBuilder>? typeVariablesByName =
+        checkTypeVariables(typeVariables, viewBuilder);
+    void setParent(String name, MemberBuilder? member) {
+      while (member != null) {
+        member.parent = viewBuilder;
+        member = member.next as MemberBuilder?;
+      }
+    }
+
+    void setParentAndCheckConflicts(String name, Builder member) {
+      if (typeVariablesByName != null) {
+        TypeVariableBuilder? tv = typeVariablesByName[name];
+        if (tv != null) {
+          viewBuilder.addProblem(
+              templateConflictsWithTypeVariable.withArguments(name),
+              member.charOffset,
+              name.length,
+              context: [
+                messageConflictsWithTypeVariableCause.withLocation(
+                    tv.fileUri!, tv.charOffset, name.length)
+              ]);
+        }
+      }
+      setParent(name, member as MemberBuilder);
+    }
+
+    members.forEach(setParentAndCheckConflicts);
+    constructors.forEach(setParentAndCheckConflicts);
+    setters.forEach(setParentAndCheckConflicts);
+    addBuilder(viewBuilder.name, viewBuilder, nameOffset,
+        getterReference: referenceFrom?.reference);
+  }
+
   TypeBuilder? _applyMixins(
       TypeBuilder? supertype,
       MixinApplicationBuilder? mixinApplications,
@@ -3113,6 +3186,9 @@
       } else if (declaration is SourceExtensionBuilder) {
         declaration.buildOutlineExpressions(classHierarchy,
             delayedActionPerformers, delayedDefaultValueCloners);
+      } else if (declaration is SourceViewBuilder) {
+        declaration.buildOutlineExpressions(classHierarchy,
+            delayedActionPerformers, delayedDefaultValueCloners);
       } else if (declaration is SourceMemberBuilder) {
         declaration.buildOutlineExpressions(classHierarchy,
             delayedActionPerformers, delayedDefaultValueCloners);
@@ -3153,6 +3229,12 @@
         }
         library.addExtension(extension);
       }
+    } else if (declaration is SourceViewBuilder) {
+      View view = declaration.build(coreLibrary,
+          addMembersToLibrary: !declaration.isDuplicate);
+      if (!declaration.isPatch && !declaration.isDuplicate) {
+        library.addView(view);
+      }
     } else if (declaration is SourceMemberBuilder) {
       declaration
           .buildOutlineNodes((Member member, BuiltMemberKind memberKind) {
@@ -3822,13 +3904,11 @@
         count += computeDefaultTypesForVariables(declaration.typeVariables,
             inErrorRecovery: issues.isNotEmpty);
 
-        Iterator<MemberBuilder> constructorIterator =
-            declaration.constructorScope.filteredIterator(
-                parent: declaration,
-                includeDuplicates: false,
-                includeAugmentations: true);
-        while (constructorIterator.moveNext()) {
-          MemberBuilder member = constructorIterator.current;
+        Iterator<MemberBuilder> iterator = declaration.constructorScope
+            .filteredIterator(
+                includeDuplicates: false, includeAugmentations: true);
+        while (iterator.moveNext()) {
+          MemberBuilder member = iterator.current;
           List<FormalParameterBuilder>? formals;
           if (member is SourceFactoryBuilder) {
             assert(member.isFactory,
@@ -3871,7 +3951,7 @@
             }
           }
         }
-      } else if (declaration is TypeAliasBuilder) {
+      } else if (declaration is SourceTypeAliasBuilder) {
         List<NonSimplicityIssue> issues = getNonSimplicityIssuesForDeclaration(
             declaration,
             performErrorRecovery: true);
@@ -3906,12 +3986,7 @@
         count += computeDefaultTypesForVariables(declaration.typeParameters,
             inErrorRecovery: issues.isNotEmpty);
 
-        Iterator<Builder> memberIterator = declaration.scope.filteredIterator(
-            parent: declaration,
-            includeDuplicates: false,
-            includeAugmentations: true);
-        while (memberIterator.moveNext()) {
-          Builder member = memberIterator.current;
+        declaration.forEach((String name, Builder member) {
           if (member is SourceProcedureBuilder) {
             processSourceProcedureBuilder(member);
           } else if (member is SourceFieldBuilder) {
@@ -3923,7 +3998,28 @@
             throw new StateError(
                 "Unexpected extension member $member (${member.runtimeType}).");
           }
-        }
+        });
+      } else if (declaration is SourceViewBuilder) {
+        List<NonSimplicityIssue> issues = getNonSimplicityIssuesForDeclaration(
+            declaration,
+            performErrorRecovery: true);
+        reportIssues(issues);
+        count += computeDefaultTypesForVariables(declaration.typeParameters,
+            inErrorRecovery: issues.isNotEmpty);
+
+        declaration.forEach((String name, Builder member) {
+          if (member is SourceProcedureBuilder) {
+            processSourceProcedureBuilder(member);
+          } else if (member is SourceFieldBuilder) {
+            if (member.type is! OmittedTypeBuilder) {
+              _recursivelyReportGenericFunctionTypesAsBoundsForType(
+                  member.type);
+            }
+          } else {
+            throw new StateError(
+                "Unexpected extension member $member (${member.runtimeType}).");
+          }
+        });
       } else if (declaration is SourceFieldBuilder) {
         if (declaration.type is! OmittedTypeBuilder) {
           List<NonSimplicityIssue> issues =
@@ -4016,6 +4112,9 @@
       } else if (builder is SourceExtensionBuilder) {
         count +=
             builder.buildBodyNodes(addMembersToLibrary: !builder.isDuplicate);
+      } else if (builder is SourceViewBuilder) {
+        count +=
+            builder.buildBodyNodes(addMembersToLibrary: !builder.isDuplicate);
       } else if (builder is SourceClassBuilder) {
         count += builder.buildBodyNodes();
       } else if (builder is SourceTypeAliasBuilder) {
@@ -4486,6 +4585,8 @@
         declaration.checkTypesInOutline(typeEnvironment);
       } else if (declaration is SourceExtensionBuilder) {
         declaration.checkTypesInOutline(typeEnvironment);
+      } else if (declaration is SourceViewBuilder) {
+        declaration.checkTypesInOutline(typeEnvironment);
       } else if (declaration is SourceTypeAliasBuilder) {
         // Do nothing.
       } else {
@@ -4922,6 +5023,7 @@
   unnamedMixinApplication,
   namedMixinApplication,
   extensionDeclaration,
+  viewDeclaration,
   typedef,
   staticMethod,
   instanceMethod,
@@ -5062,6 +5164,18 @@
     _typeVariables = typeVariables;
   }
 
+  /// Registers that this builder is preparing for a view declaration with the
+  /// given [name] and [typeVariables] located [charOffset].
+  void markAsViewDeclaration(
+      String name, int charOffset, List<TypeVariableBuilder>? typeVariables) {
+    assert(_kind == TypeParameterScopeKind.classOrNamedMixinApplication,
+        "Unexpected declaration kind: $_kind");
+    _kind = TypeParameterScopeKind.viewDeclaration;
+    _name = name;
+    _charOffset = charOffset;
+    _typeVariables = typeVariables;
+  }
+
   /// Returns `true` if this scope builder is for an unnamed extension
   /// declaration.
   bool get isUnnamedExtension => extensionName?.isUnnamedExtension ?? false;
diff --git a/pkg/front_end/lib/src/fasta/source/source_view_builder.dart b/pkg/front_end/lib/src/fasta/source/source_view_builder.dart
new file mode 100644
index 0000000..e9424e2
--- /dev/null
+++ b/pkg/front_end/lib/src/fasta/source/source_view_builder.dart
@@ -0,0 +1,170 @@
+// 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:front_end/src/fasta/source/source_builder_mixins.dart';
+import 'package:kernel/ast.dart';
+
+import '../../base/common.dart';
+import '../builder/builder.dart';
+import '../builder/library_builder.dart';
+import '../builder/metadata_builder.dart';
+import '../builder/type_variable_builder.dart';
+import '../builder/view_builder.dart';
+import '../fasta_codes.dart'
+    show
+        messagePatchDeclarationMismatch,
+        messagePatchDeclarationOrigin,
+        noLength;
+import '../problems.dart';
+import '../scope.dart';
+import 'source_library_builder.dart';
+import 'source_member_builder.dart';
+
+class SourceViewBuilder extends ViewBuilderImpl
+    with SourceDeclarationBuilderMixin {
+  final View _view;
+
+  SourceViewBuilder? _origin;
+  SourceViewBuilder? patchForTesting;
+
+  MergedClassMemberScope? _mergedScope;
+
+  @override
+  final List<TypeVariableBuilder>? typeParameters;
+
+  SourceViewBuilder(
+      List<MetadataBuilder>? metadata,
+      int modifiers,
+      String name,
+      this.typeParameters,
+      Scope scope,
+      SourceLibraryBuilder parent,
+      int startOffset,
+      int nameOffset,
+      int endOffset,
+      Extension? referenceFrom)
+      : _view = new View(
+            name: name,
+            fileUri: parent.fileUri,
+            typeParameters:
+                TypeVariableBuilder.typeParametersFromBuilders(typeParameters),
+            reference: referenceFrom?.reference)
+          ..fileOffset = nameOffset,
+        super(metadata, modifiers, name, parent, nameOffset, scope);
+
+  @override
+  SourceLibraryBuilder get libraryBuilder =>
+      super.libraryBuilder as SourceLibraryBuilder;
+
+  @override
+  SourceViewBuilder get origin => _origin ?? this;
+
+  // TODO(johnniwinther): Add merged scope for views.
+  MergedClassMemberScope get mergedScope => _mergedScope ??= isPatch
+      ? origin.mergedScope
+      : throw new UnimplementedError("SourceViewBuilder.mergedScope");
+
+  @override
+  View get view => isPatch ? origin._view : _view;
+
+  @override
+  Annotatable get annotatable => view;
+
+  /// Builds the [View] for this view builder and inserts the members
+  /// into the [Library] of [libraryBuilder].
+  ///
+  /// [addMembersToLibrary] is `true` if the view members should be added
+  /// to the library. This is `false` if the view is in conflict with
+  /// another library member. In this case, the view member should not be
+  /// added to the library to avoid name clashes with other members in the
+  /// library.
+  View build(LibraryBuilder coreLibrary, {required bool addMembersToLibrary}) {
+    // TODO(johnniwinther): Find and build the representation type.
+    _view.representationType = const DynamicType();
+
+    buildInternal(coreLibrary, addMembersToLibrary: addMembersToLibrary);
+
+    return _view;
+  }
+
+  @override
+  void addMemberDescriptorInternal(SourceMemberBuilder memberBuilder,
+      Member member, BuiltMemberKind memberKind, Reference memberReference) {
+    String name = memberBuilder.name;
+    ViewMemberKind kind;
+    switch (memberKind) {
+      case BuiltMemberKind.Constructor:
+      case BuiltMemberKind.RedirectingFactory:
+      case BuiltMemberKind.Field:
+      case BuiltMemberKind.Method:
+        unhandled("${member.runtimeType}:${memberKind}", "buildMembers",
+            memberBuilder.charOffset, memberBuilder.fileUri);
+      case BuiltMemberKind.ExtensionField:
+      case BuiltMemberKind.LateIsSetField:
+        kind = ViewMemberKind.Field;
+        break;
+      case BuiltMemberKind.ExtensionMethod:
+        kind = ViewMemberKind.Method;
+        break;
+      case BuiltMemberKind.ExtensionGetter:
+      case BuiltMemberKind.LateGetter:
+        kind = ViewMemberKind.Getter;
+        break;
+      case BuiltMemberKind.ExtensionSetter:
+      case BuiltMemberKind.LateSetter:
+        kind = ViewMemberKind.Setter;
+        break;
+      case BuiltMemberKind.ExtensionOperator:
+        kind = ViewMemberKind.Operator;
+        break;
+      case BuiltMemberKind.ExtensionTearOff:
+        kind = ViewMemberKind.TearOff;
+        break;
+    }
+    // ignore: unnecessary_null_comparison
+    assert(kind != null);
+    view.members.add(new ViewMemberDescriptor(
+        name: new Name(name, libraryBuilder.library),
+        member: memberReference,
+        isStatic: memberBuilder.isStatic,
+        kind: kind));
+  }
+
+  @override
+  void applyPatch(Builder patch) {
+    if (patch is SourceViewBuilder) {
+      patch._origin = this;
+      if (retainDataForTesting) {
+        patchForTesting = patch;
+      }
+      scope.forEachLocalMember((String name, Builder member) {
+        Builder? memberPatch =
+            patch.scope.lookupLocalMember(name, setter: false);
+        if (memberPatch != null) {
+          member.applyPatch(memberPatch);
+        }
+      });
+      scope.forEachLocalSetter((String name, Builder member) {
+        Builder? memberPatch =
+            patch.scope.lookupLocalMember(name, setter: true);
+        if (memberPatch != null) {
+          member.applyPatch(memberPatch);
+        }
+      });
+
+      // TODO(johnniwinther): Check that type parameters and on-type match
+      // with origin declaration.
+    } else {
+      libraryBuilder.addProblem(messagePatchDeclarationMismatch,
+          patch.charOffset, noLength, patch.fileUri, context: [
+        messagePatchDeclarationOrigin.withLocation(
+            fileUri, charOffset, noLength)
+      ]);
+    }
+  }
+
+  // TODO(johnniwinther): Implement representationType.
+  @override
+  DartType get representationType => throw new UnimplementedError();
+}
diff --git a/pkg/front_end/testcases/views/view_class_declaration.dart b/pkg/front_end/testcases/views/view_class_declaration.dart
index 041a686..277547e 100644
--- a/pkg/front_end/testcases/views/view_class_declaration.dart
+++ b/pkg/front_end/testcases/views/view_class_declaration.dart
@@ -4,4 +4,7 @@
 
 mixin Mixin {}
 view class Class1 {}
-view class Class2 = Object with Mixin;
\ No newline at end of file
+view class Class2 = Object with Mixin;
+view class Class3<T> {}
+
+method(Class1 c1, Class3<int> c3) {}
\ No newline at end of file
diff --git a/pkg/front_end/testcases/views/view_class_declaration.dart.strong.expect b/pkg/front_end/testcases/views/view_class_declaration.dart.strong.expect
index e7e05fa..d89018f 100644
--- a/pkg/front_end/testcases/views/view_class_declaration.dart.strong.expect
+++ b/pkg/front_end/testcases/views/view_class_declaration.dart.strong.expect
@@ -4,13 +4,13 @@
 
 abstract class Mixin extends core::Object /*isMixinDeclaration*/  {
 }
-class Class1 extends core::Object {
-  synthetic constructor •() → self::Class1
-    : super core::Object::•()
-    ;
-}
 class Class2 = core::Object with self::Mixin /*hasConstConstructor*/  {
   const synthetic constructor •() → self::Class2
     : super core::Object::•()
     ;
 }
+view Class1 /* representationType = dynamic */ {
+}
+view Class3<T extends core::Object? = dynamic> /* representationType = dynamic */ {
+}
+static method method(self::Class1 c1, self::Class3<core::int> c3) → dynamic {}
diff --git a/pkg/front_end/testcases/views/view_class_declaration.dart.strong.transformed.expect b/pkg/front_end/testcases/views/view_class_declaration.dart.strong.transformed.expect
index ad230b2..42a6490 100644
--- a/pkg/front_end/testcases/views/view_class_declaration.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/views/view_class_declaration.dart.strong.transformed.expect
@@ -4,13 +4,13 @@
 
 abstract class Mixin extends core::Object /*isMixinDeclaration*/  {
 }
-class Class1 extends core::Object {
-  synthetic constructor •() → self::Class1
-    : super core::Object::•()
-    ;
-}
 class Class2 extends core::Object implements self::Mixin /*isEliminatedMixin,hasConstConstructor*/  {
   const synthetic constructor •() → self::Class2
     : super core::Object::•()
     ;
 }
+view Class1 /* representationType = dynamic */ {
+}
+view Class3<T extends core::Object? = dynamic> /* representationType = dynamic */ {
+}
+static method method(self::Class1 c1, self::Class3<core::int> c3) → dynamic {}
diff --git a/pkg/front_end/testcases/views/view_class_declaration.dart.textual_outline.expect b/pkg/front_end/testcases/views/view_class_declaration.dart.textual_outline.expect
index eb7f7f0..bbb1ac9 100644
--- a/pkg/front_end/testcases/views/view_class_declaration.dart.textual_outline.expect
+++ b/pkg/front_end/testcases/views/view_class_declaration.dart.textual_outline.expect
@@ -3,3 +3,6 @@
 class Class1 {}
 view
 class Class2 = Object with Mixin;
+view
+class Class3<T> {}
+method(Class1 c1, Class3<int> c3) {}
diff --git a/pkg/front_end/testcases/views/view_class_declaration.dart.weak.expect b/pkg/front_end/testcases/views/view_class_declaration.dart.weak.expect
index e7e05fa..d89018f 100644
--- a/pkg/front_end/testcases/views/view_class_declaration.dart.weak.expect
+++ b/pkg/front_end/testcases/views/view_class_declaration.dart.weak.expect
@@ -4,13 +4,13 @@
 
 abstract class Mixin extends core::Object /*isMixinDeclaration*/  {
 }
-class Class1 extends core::Object {
-  synthetic constructor •() → self::Class1
-    : super core::Object::•()
-    ;
-}
 class Class2 = core::Object with self::Mixin /*hasConstConstructor*/  {
   const synthetic constructor •() → self::Class2
     : super core::Object::•()
     ;
 }
+view Class1 /* representationType = dynamic */ {
+}
+view Class3<T extends core::Object? = dynamic> /* representationType = dynamic */ {
+}
+static method method(self::Class1 c1, self::Class3<core::int> c3) → dynamic {}
diff --git a/pkg/front_end/testcases/views/view_class_declaration.dart.weak.modular.expect b/pkg/front_end/testcases/views/view_class_declaration.dart.weak.modular.expect
index e7e05fa..d89018f 100644
--- a/pkg/front_end/testcases/views/view_class_declaration.dart.weak.modular.expect
+++ b/pkg/front_end/testcases/views/view_class_declaration.dart.weak.modular.expect
@@ -4,13 +4,13 @@
 
 abstract class Mixin extends core::Object /*isMixinDeclaration*/  {
 }
-class Class1 extends core::Object {
-  synthetic constructor •() → self::Class1
-    : super core::Object::•()
-    ;
-}
 class Class2 = core::Object with self::Mixin /*hasConstConstructor*/  {
   const synthetic constructor •() → self::Class2
     : super core::Object::•()
     ;
 }
+view Class1 /* representationType = dynamic */ {
+}
+view Class3<T extends core::Object? = dynamic> /* representationType = dynamic */ {
+}
+static method method(self::Class1 c1, self::Class3<core::int> c3) → dynamic {}
diff --git a/pkg/front_end/testcases/views/view_class_declaration.dart.weak.outline.expect b/pkg/front_end/testcases/views/view_class_declaration.dart.weak.outline.expect
index 6c64557..df84fe7 100644
--- a/pkg/front_end/testcases/views/view_class_declaration.dart.weak.outline.expect
+++ b/pkg/front_end/testcases/views/view_class_declaration.dart.weak.outline.expect
@@ -4,12 +4,14 @@
 
 abstract class Mixin extends core::Object /*isMixinDeclaration*/  {
 }
-class Class1 extends core::Object {
-  synthetic constructor •() → self::Class1
-    ;
-}
 class Class2 = core::Object with self::Mixin /*hasConstConstructor*/  {
   const synthetic constructor •() → self::Class2
     : super core::Object::•()
     ;
 }
+view Class1 /* representationType = dynamic */ {
+}
+view Class3<T extends core::Object? = dynamic> /* representationType = dynamic */ {
+}
+static method method(self::Class1 c1, self::Class3<core::int> c3) → dynamic
+  ;
diff --git a/pkg/front_end/testcases/views/view_class_declaration.dart.weak.transformed.expect b/pkg/front_end/testcases/views/view_class_declaration.dart.weak.transformed.expect
index ad230b2..42a6490 100644
--- a/pkg/front_end/testcases/views/view_class_declaration.dart.weak.transformed.expect
+++ b/pkg/front_end/testcases/views/view_class_declaration.dart.weak.transformed.expect
@@ -4,13 +4,13 @@
 
 abstract class Mixin extends core::Object /*isMixinDeclaration*/  {
 }
-class Class1 extends core::Object {
-  synthetic constructor •() → self::Class1
-    : super core::Object::•()
-    ;
-}
 class Class2 extends core::Object implements self::Mixin /*isEliminatedMixin,hasConstConstructor*/  {
   const synthetic constructor •() → self::Class2
     : super core::Object::•()
     ;
 }
+view Class1 /* representationType = dynamic */ {
+}
+view Class3<T extends core::Object? = dynamic> /* representationType = dynamic */ {
+}
+static method method(self::Class1 c1, self::Class3<core::int> c3) → dynamic {}
diff --git a/pkg/kernel/lib/ast.dart b/pkg/kernel/lib/ast.dart
index cb17399..35b86f9 100644
--- a/pkg/kernel/lib/ast.dart
+++ b/pkg/kernel/lib/ast.dart
@@ -12470,10 +12470,7 @@
           viewReference, typeArguments, declaredNullability);
 
   @override
-  Nullability get nullability {
-    return uniteNullabilities(
-        declaredNullability, view.representationType.nullability);
-  }
+  Nullability get nullability => declaredNullability;
 
   @override
   DartType get resolveTypeParameterType =>
diff --git a/pkg/kernel/lib/text/ast_to_text.dart b/pkg/kernel/lib/text/ast_to_text.dart
index 233eeec..019fbe4 100644
--- a/pkg/kernel/lib/text/ast_to_text.dart
+++ b/pkg/kernel/lib/text/ast_to_text.dart
@@ -343,6 +343,10 @@
     return node.name;
   }
 
+  String getViewName(View node) {
+    return node.name;
+  }
+
   String getClassReference(Class node) {
     // ignore: unnecessary_null_comparison
     if (node == null) return '<No Class>';
@@ -359,6 +363,14 @@
     return '$library::$name';
   }
 
+  String getViewReference(View node) {
+    // ignore: unnecessary_null_comparison
+    if (node == null) return '<No View>';
+    String name = getViewName(node);
+    String library = getLibraryReference(node.enclosingLibrary);
+    return '$library::$name';
+  }
+
   String getTypedefReference(Typedef node) {
     // ignore: unnecessary_null_comparison
     if (node == null) return '<No Typedef>';
@@ -504,6 +516,7 @@
     library.typedefs.forEach(writeNode);
     library.classes.forEach(writeNode);
     library.extensions.forEach(writeNode);
+    library.views.forEach(writeNode);
     library.fields.forEach(writeNode);
     library.procedures.forEach(writeNode);
   }
@@ -984,6 +997,22 @@
     throw "Neither node nor canonical name found";
   }
 
+  void writeViewReferenceFromReference(Reference reference) {
+    writeWord(getViewReferenceFromReference(reference));
+  }
+
+  String getViewReferenceFromReference(Reference reference) {
+    // ignore: unnecessary_null_comparison
+    if (reference == null) return '<No Extension>';
+    if (reference.node != null) {
+      return getViewReference(reference.asView);
+    }
+    if (reference.canonicalName != null) {
+      return getCanonicalNameString(reference.canonicalName!);
+    }
+    throw "Neither node nor canonical name found";
+  }
+
   void writeMemberReferenceFromReference(Reference? reference) {
     writeWord(getMemberReferenceFromReference(reference));
   }
@@ -1447,6 +1476,71 @@
   }
 
   @override
+  void visitView(View node) {
+    writeAnnotationList(node.annotations);
+    writeIndentation();
+    writeWord('view');
+    writeWord(getViewName(node));
+    writeTypeParameterList(node.typeParameters);
+    writeWord('/* representationType =');
+    writeType(node.representationType);
+    writeWord('*/');
+
+    String endLineString = ' {';
+    if (node.enclosingLibrary.fileUri != node.fileUri) {
+      endLineString += ' // from ${node.fileUri}';
+    }
+
+    endLine(endLineString);
+    ++indentation;
+    node.members.forEach((ViewMemberDescriptor descriptor) {
+      writeIndentation();
+      writeModifier(descriptor.isStatic, 'static');
+      switch (descriptor.kind) {
+        case ViewMemberKind.Constructor:
+          writeWord('constructor');
+          break;
+        case ViewMemberKind.Factory:
+          writeWord('factory');
+          break;
+        case ViewMemberKind.Method:
+          writeWord('method');
+          break;
+        case ViewMemberKind.Getter:
+          writeWord('get');
+          break;
+        case ViewMemberKind.Setter:
+          writeWord('set');
+          break;
+        case ViewMemberKind.Operator:
+          writeWord('operator');
+          break;
+        case ViewMemberKind.Field:
+          writeWord('field');
+          break;
+        case ViewMemberKind.TearOff:
+          writeWord('tearoff');
+          break;
+      }
+      writeName(descriptor.name);
+      writeSpaced('=');
+      Member member = descriptor.member.asMember;
+      if (member is Procedure) {
+        if (member.isGetter) {
+          writeWord('get');
+        } else if (member.isSetter) {
+          writeWord('set');
+        }
+      }
+      writeMemberReferenceFromReference(descriptor.member);
+      endLine(';');
+    });
+    --indentation;
+    writeIndentation();
+    endLine('}');
+  }
+
+  @override
   void visitTypedef(Typedef node) {
     writeAnnotationList(node.annotations);
     writeIndentation();
@@ -2678,6 +2772,18 @@
   }
 
   @override
+  void visitViewType(ViewType node) {
+    writeViewReferenceFromReference(node.viewReference);
+    if (node.typeArguments.isNotEmpty) {
+      writeSymbol('<');
+      writeList(node.typeArguments, writeType);
+      writeSymbol('>');
+      state = Printer.WORD;
+    }
+    writeNullability(node.declaredNullability);
+  }
+
+  @override
   void visitFutureOrType(FutureOrType node) {
     writeWord('FutureOr');
     writeSymbol('<');
diff --git a/pkg/vm_service/lib/src/vm_service.dart b/pkg/vm_service/lib/src/vm_service.dart
index 782a241..9f30e1c 100644
--- a/pkg/vm_service/lib/src/vm_service.dart
+++ b/pkg/vm_service/lib/src/vm_service.dart
@@ -7120,15 +7120,18 @@
   /// An object that is part of a retaining path.
   ObjRef? value;
 
-  /// The offset of the retaining object in a containing list.
+  /// If `value` is a List, `parentListIndex` is the index where the previous
+  /// object on the retaining path is located.
   @optional
   int? parentListIndex;
 
-  /// The key mapping to the retaining object in a containing map.
+  /// If `value` is a Map, `parentMapKey` is the key mapping to the previous
+  /// object on the retaining path.
   @optional
   ObjRef? parentMapKey;
 
-  /// The name of the field containing the retaining object within an object.
+  /// If `value` is a non-List, non-Map object, `parentField` is the name of the
+  /// field containing the previous object on the retaining path.
   @optional
   String? parentField;
 
diff --git a/runtime/observatory/lib/src/elements/helpers/any_ref.dart b/runtime/observatory/lib/src/elements/helpers/any_ref.dart
index ac5e1e3..222f40d 100644
--- a/runtime/observatory/lib/src/elements/helpers/any_ref.dart
+++ b/runtime/observatory/lib/src/elements/helpers/any_ref.dart
@@ -93,6 +93,8 @@
     }
   } else if (ref is M.Sentinel) {
     return new SentinelValueElement(ref, queue: queue).element;
+  } else if (ref is num || ref is String) {
+    return new SpanElement()..text = ref.toString();
   }
   throw new Exception('Unknown ref type (${ref.runtimeType})');
 }
diff --git a/runtime/observatory/lib/src/elements/instance_ref.dart b/runtime/observatory/lib/src/elements/instance_ref.dart
index 819a499..01ba033 100644
--- a/runtime/observatory/lib/src/elements/instance_ref.dart
+++ b/runtime/observatory/lib/src/elements/instance_ref.dart
@@ -128,6 +128,7 @@
       case M.InstanceKind.functionType:
       case M.InstanceKind.typeRef:
       case M.InstanceKind.typeParameter:
+      case M.InstanceKind.recordType:
         return [
           new AnchorElement(href: Uris.inspect(_isolate, object: _instance))
             ..text = _instance.name
@@ -194,12 +195,10 @@
             ]
         ];
       case M.InstanceKind.mirrorReference:
-        return [
-          new AnchorElement(href: Uris.inspect(_isolate, object: _instance))
-            ..classes = ['emphasize']
-            ..text = _instance.clazz!.name
-        ];
       case M.InstanceKind.weakProperty:
+      case M.InstanceKind.finalizer:
+      case M.InstanceKind.weakReference:
+      case M.InstanceKind.record:
         return [
           new AnchorElement(href: Uris.inspect(_isolate, object: _instance))
             ..classes = ['emphasize']
@@ -216,6 +215,7 @@
       case M.InstanceKind.mirrorReference:
       case M.InstanceKind.stackTrace:
       case M.InstanceKind.weakProperty:
+      case M.InstanceKind.recordType:
         return true;
       case M.InstanceKind.list:
       case M.InstanceKind.map:
@@ -339,6 +339,18 @@
                   queue: _r.queue)
               .element,
         ];
+      case M.InstanceKind.recordType:
+        final fields = _loadedInstance!.fields!.toList();
+        return [
+          for (int i = 0; i < fields.length; ++i) ...[
+            new SpanElement()..text = '${fields[i].name} = ',
+            new InstanceRefElement(
+                    _isolate, fields[i].value!.asValue!, _objects,
+                    queue: _r.queue)
+                .element,
+            if (i + 1 != fields.length) new BRElement(),
+          ]
+        ];
       default:
         return [];
     }
diff --git a/runtime/observatory/lib/src/elements/instance_view.dart b/runtime/observatory/lib/src/elements/instance_view.dart
index 2063c28..4c4d690 100644
--- a/runtime/observatory/lib/src/elements/instance_view.dart
+++ b/runtime/observatory/lib/src/elements/instance_view.dart
@@ -335,9 +335,12 @@
                     ..content = <Element>[
                       new DivElement()
                         ..classes = ['memberList']
-                        ..children = fields
-                            .map<Element>((f) => member(f.decl, f.value))
-                            .toList()
+                        ..children = fields.map<Element>((f) {
+                          final name = _instance.kind == M.InstanceKind.record
+                              ? f.name
+                              : f.decl;
+                          return member(name, f.value);
+                        }).toList()
                     ])
                   .element
             ]
diff --git a/runtime/observatory/lib/src/models/objects/instance.dart b/runtime/observatory/lib/src/models/objects/instance.dart
index 8102095..1b2d58e 100644
--- a/runtime/observatory/lib/src/models/objects/instance.dart
+++ b/runtime/observatory/lib/src/models/objects/instance.dart
@@ -126,6 +126,18 @@
 
   /// An instance of the Dart class RawReceivePort
   receivePort,
+
+  /// An instance of Record.
+  record,
+
+  /// An instance of RecordType
+  recordType,
+
+  /// An instance of Finalizer
+  finalizer,
+
+  /// An instance of WeakReference
+  weakReference,
 }
 
 bool isTypedData(InstanceKind? kind) {
@@ -463,7 +475,8 @@
 
 abstract class BoundField {
   FieldRef? get decl;
-  Guarded<InstanceRef>? get value;
+  Guarded<dynamic>? get value;
+  dynamic get name;
 }
 
 abstract class NativeField {
diff --git a/runtime/observatory/lib/src/service/object.dart b/runtime/observatory/lib/src/service/object.dart
index c4af948..1a256a6 100644
--- a/runtime/observatory/lib/src/service/object.dart
+++ b/runtime/observatory/lib/src/service/object.dart
@@ -2108,7 +2108,7 @@
     _map.clear();
     map.forEach((k, v) => _map[k] = v);
 
-    name = _map['name'];
+    name = _map['name']?.toString();
     vmName = (_map.containsKey('_vmName') ? _map['_vmName'] : name);
   }
 
@@ -2791,25 +2791,33 @@
       return M.InstanceKind.typeRef;
     case 'ReceivePort':
       return M.InstanceKind.receivePort;
+    case '_RecordType':
+      return M.InstanceKind.recordType;
+    case '_Record':
+      return M.InstanceKind.record;
+    case 'Finalizer':
+      return M.InstanceKind.finalizer;
+    case 'WeakReference':
+      return M.InstanceKind.weakReference;
   }
   var message = 'Unrecognized instance kind: $s';
   Logger.root.severe(message);
   throw new ArgumentError(message);
 }
 
-class Guarded<T extends ServiceObject> implements M.Guarded<T> {
+class Guarded<T> implements M.Guarded<T> {
   bool get isValue => asValue != null;
   bool get isSentinel => asSentinel != null;
   final Sentinel? asSentinel;
   final T? asValue;
 
-  factory Guarded(ServiceObject obj) {
+  factory Guarded(dynamic obj) {
     if (obj is Sentinel) {
       return new Guarded.fromSentinel(obj);
     } else if (obj is T) {
       return new Guarded.fromValue(obj);
     }
-    throw new Exception('${obj.type} is neither Sentinel or $T');
+    throw new Exception('${obj.runtimeType} is neither Sentinel or $T');
   }
 
   Guarded.fromSentinel(this.asSentinel) : asValue = null;
@@ -2817,9 +2825,11 @@
 }
 
 class BoundField implements M.BoundField {
-  final Field decl;
-  final Guarded<Instance> value;
-  BoundField(this.decl, value) : value = new Guarded(value);
+  final Field? decl;
+  // String|int
+  final dynamic name;
+  final Guarded<dynamic> value;
+  BoundField(this.decl, this.name, value) : value = new Guarded(value);
 }
 
 class NativeField implements M.NativeField {
@@ -2916,7 +2926,7 @@
   Instance._empty(ServiceObjectOwner? owner) : super._empty(owner);
 
   void _update(Map map, bool mapIsRef) {
-    // Extract full properties.1
+    // Extract full properties.
     _upgradeCollection(map, isolate);
     super._update(map, mapIsRef);
 
@@ -2925,7 +2935,7 @@
     // Coerce absence to false.
     valueAsStringIsTruncated = map['valueAsStringIsTruncated'] == true;
     closureFunction = map['closureFunction'];
-    name = map['name'];
+    name = map['name']?.toString();
     length = map['length'];
     pattern = map['pattern'];
     typeClass = map['typeClass'];
@@ -2958,7 +2968,7 @@
     if (map['fields'] != null) {
       var fields = <BoundField>[];
       for (var f in map['fields']) {
-        fields.add(new BoundField(f['decl'], f['value']));
+        fields.add(new BoundField(f['decl'], f['name'], f['value']));
       }
       this.fields = fields;
     } else {
diff --git a/runtime/observatory/tests/ui/inspector.dart b/runtime/observatory/tests/ui/inspector.dart
index e8fc90a..eeb461f 100644
--- a/runtime/observatory/tests/ui/inspector.dart
+++ b/runtime/observatory/tests/ui/inspector.dart
@@ -2,6 +2,8 @@
 // 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.
 
+// @dart = 2.19
+
 // See inspector.txt for expected behavior.
 
 library manual_inspector_test;
@@ -43,10 +45,13 @@
   var blockCopying;
   var blockFull;
   var blockFullWithChain;
+  var blockType;
   var boundedType;
   var capability;
   var counter;
   var expando;
+  var finalizer;
+  var finalizerEntry;
   var float32x4;
   var float64;
   var float64x2;
@@ -62,7 +67,10 @@
   var mirrorReference;
   var portReceive;
   var portSend;
+  var record;
+  var recordType;
   var regex;
+  late var sentinel;  // Not initialized
   var smi;
   var stacktrace;
   var string;
@@ -76,9 +84,12 @@
   var theTrue;
   var type;
   var typeParameter;
-  var typedData;
+  var typedDataArray;
+  var typedDataView;
+  var typedDataUnmodifiableView;
   var userTag;
   var weakProperty;
+  var weakReference;
 
   genStackTrace() {
     try {
@@ -141,16 +152,19 @@
     array[0] = 1;
     array[1] = 2;
     array[2] = 3;
-    bigint = 1 << 65;
+    bigint = BigInt.one << 65;
     blockClean = genCleanBlock();
     blockCopying = genCopyingBlock();
     blockFull = genFullBlock();
     blockFullWithChain = genFullBlockWithChain();
+    blockType = blockClean.runtimeType;
     boundedType = extractPrivateField(
         reflect(new B<int>()).type.typeVariables.single, '_reflectee');
     counter = new Counter("CounterName", "Counter description");
     expando = new Expando("expando-name");
     expando[array] = 'The weakly associated value';
+    finalizer = Finalizer<dynamic>((_){});
+    finalizer.attach(this, this);
     float32x4 = new Float32x4(0.0, -1.0, 3.14, 2e28);
     float64 = 3.14;
     float64x2 = new Float64x2(0.0, 3.14);
@@ -170,6 +184,8 @@
     mirrorReference = extractPrivateField(mirrorClass, '_reflectee');
     portReceive = new RawReceivePort();
     portSend = portReceive.sendPort;
+    record = (1, 2, three: 3, four: 4);
+    recordType = record.runtimeType;
     regex = new RegExp("a*b+c");
     smi = 7;
     stacktrace = genStackTrace();
@@ -185,10 +201,13 @@
     type = String;
     typeParameter =
         extractPrivateField(reflectClass(A).typeVariables.single, '_reflectee');
-    typedData = extractPrivateField(new ByteData(64), '_typedData');
+    typedDataArray = Uint8List(32);
+    typedDataView = Uint8List.view(typedDataArray.buffer, 16);
+    typedDataUnmodifiableView = UnmodifiableUint8ListView(typedDataArray);
     userTag = new UserTag("Example tag name");
     weakProperty =
         extractPrivateField(expando, '_data').firstWhere((e) => e != null);
+    weakReference = WeakReference(this);
 
     Isolate.spawn(secondMain, "Hello2").then((otherIsolate) {
       isolate = otherIsolate;
diff --git a/runtime/observatory/tests/ui/inspector_part.dart b/runtime/observatory/tests/ui/inspector_part.dart
index 06589e3..6222354 100644
--- a/runtime/observatory/tests/ui/inspector_part.dart
+++ b/runtime/observatory/tests/ui/inspector_part.dart
@@ -2,6 +2,8 @@
 // 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.
 
+// @dart = 2.19
+
 part of manual_inspector_test;
 
 functionInPart() {}
diff --git a/runtime/observatory_2/lib/src/elements/helpers/any_ref.dart b/runtime/observatory_2/lib/src/elements/helpers/any_ref.dart
index 0ae2930..a1f2d96 100644
--- a/runtime/observatory_2/lib/src/elements/helpers/any_ref.dart
+++ b/runtime/observatory_2/lib/src/elements/helpers/any_ref.dart
@@ -93,6 +93,8 @@
     }
   } else if (ref is M.Sentinel) {
     return new SentinelValueElement(ref, queue: queue).element;
+  } else if (ref is num || ref is String) {
+    return new SpanElement()..text = ref.toString();
   }
   throw new Exception('Unknown ref type (${ref.runtimeType})');
 }
diff --git a/runtime/observatory_2/lib/src/elements/instance_ref.dart b/runtime/observatory_2/lib/src/elements/instance_ref.dart
index a4b3f9e..e98148e 100644
--- a/runtime/observatory_2/lib/src/elements/instance_ref.dart
+++ b/runtime/observatory_2/lib/src/elements/instance_ref.dart
@@ -127,6 +127,7 @@
       case M.InstanceKind.functionType:
       case M.InstanceKind.typeRef:
       case M.InstanceKind.typeParameter:
+      case M.InstanceKind.recordType:
         return [
           new AnchorElement(href: Uris.inspect(_isolate, object: _instance))
             ..text = _instance.name
@@ -192,12 +193,10 @@
             ]
         ];
       case M.InstanceKind.mirrorReference:
-        return [
-          new AnchorElement(href: Uris.inspect(_isolate, object: _instance))
-            ..classes = ['emphasize']
-            ..text = _instance.clazz.name
-        ];
       case M.InstanceKind.weakProperty:
+      case M.InstanceKind.finalizer:
+      case M.InstanceKind.weakReference:
+      case M.InstanceKind.record:
         return [
           new AnchorElement(href: Uris.inspect(_isolate, object: _instance))
             ..classes = ['emphasize']
@@ -214,6 +213,7 @@
       case M.InstanceKind.mirrorReference:
       case M.InstanceKind.stackTrace:
       case M.InstanceKind.weakProperty:
+      case M.InstanceKind.recordType:
         return true;
       case M.InstanceKind.list:
       case M.InstanceKind.map:
@@ -337,6 +337,17 @@
                   queue: _r.queue)
               .element,
         ];
+      case M.InstanceKind.recordType:
+        final fields = _loadedInstance.fields.toList();
+        return [
+          for (int i = 0; i < fields.length; ++i) ...[
+            new SpanElement()..text = '${fields[i].name} = ',
+            new InstanceRefElement(_isolate, fields[i].value.asValue, _objects,
+                    queue: _r.queue)
+                .element,
+            if (i + 1 != fields.length) new BRElement(),
+          ]
+        ];
       default:
         return [];
     }
diff --git a/runtime/observatory_2/lib/src/elements/instance_view.dart b/runtime/observatory_2/lib/src/elements/instance_view.dart
index fcc1812..592a978 100644
--- a/runtime/observatory_2/lib/src/elements/instance_view.dart
+++ b/runtime/observatory_2/lib/src/elements/instance_view.dart
@@ -334,9 +334,12 @@
                     ..content = <Element>[
                       new DivElement()
                         ..classes = ['memberList']
-                        ..children = fields
-                            .map<Element>((f) => member(f.decl, f.value))
-                            .toList()
+                        ..children = fields.map<Element>((f) {
+                          final name = _instance.kind == M.InstanceKind.record
+                              ? f.name
+                              : f.decl;
+                          return member(name, f.value);
+                        }).toList()
                     ])
                   .element
             ]
diff --git a/runtime/observatory_2/lib/src/models/objects/instance.dart b/runtime/observatory_2/lib/src/models/objects/instance.dart
index b21c572..ff914bc 100644
--- a/runtime/observatory_2/lib/src/models/objects/instance.dart
+++ b/runtime/observatory_2/lib/src/models/objects/instance.dart
@@ -126,6 +126,18 @@
 
   /// An instance of the Dart class RawReceivePort
   receivePort,
+
+  /// An instance of Record.
+  record,
+
+  /// An instance of RecordType
+  recordType,
+
+  /// An instance of Finalizer
+  finalizer,
+
+  /// An instance of WeakReference
+  weakReference,
 }
 
 bool isTypedData(InstanceKind kind) {
@@ -454,7 +466,8 @@
 
 abstract class BoundField {
   FieldRef get decl;
-  Guarded<InstanceRef> get value;
+  dynamic get name;
+  Guarded<dynamic> get value;
 }
 
 abstract class NativeField {
diff --git a/runtime/observatory_2/lib/src/service/object.dart b/runtime/observatory_2/lib/src/service/object.dart
index 5c9f6df..bfe5b05 100644
--- a/runtime/observatory_2/lib/src/service/object.dart
+++ b/runtime/observatory_2/lib/src/service/object.dart
@@ -2118,7 +2118,7 @@
     _map.clear();
     _map.addAll(map);
 
-    name = _map['name'];
+    name = _map['name']?.toString();
     vmName = (_map.containsKey('_vmName') ? _map['_vmName'] : name);
   }
 
@@ -2800,25 +2800,33 @@
       return M.InstanceKind.typeRef;
     case 'ReceivePort':
       return M.InstanceKind.receivePort;
+    case '_Record':
+      return M.InstanceKind.record;
+    case '_RecordType':
+      return M.InstanceKind.recordType;
+    case 'Finalizer':
+      return M.InstanceKind.finalizer;
+    case 'WeakReference':
+      return M.InstanceKind.weakReference;
   }
   var message = 'Unrecognized instance kind: $s';
   Logger.root.severe(message);
   throw new ArgumentError(message);
 }
 
-class Guarded<T extends ServiceObject> implements M.Guarded<T> {
+class Guarded<T> implements M.Guarded<T> {
   bool get isValue => asValue != null;
   bool get isSentinel => asSentinel != null;
   final Sentinel asSentinel;
   final T asValue;
 
-  factory Guarded(ServiceObject obj) {
+  factory Guarded(dynamic obj) {
     if (obj is Sentinel) {
       return new Guarded.fromSentinel(obj);
     } else if (obj is T) {
       return new Guarded.fromValue(obj);
     }
-    throw new Exception('${obj.type} is neither Sentinel or $T');
+    throw new Exception('${obj.runtimeType} is neither Sentinel or $T');
   }
 
   Guarded.fromSentinel(this.asSentinel) : asValue = null;
@@ -2827,8 +2835,10 @@
 
 class BoundField implements M.BoundField {
   final Field decl;
-  final Guarded<Instance> value;
-  BoundField(this.decl, value) : value = new Guarded(value);
+  // String|int
+  final dynamic name;
+  final Guarded<dynamic> value;
+  BoundField(this.decl, this.name, value) : value = new Guarded(value);
 }
 
 class NativeField implements M.NativeField {
@@ -2929,7 +2939,7 @@
   Instance._empty(ServiceObjectOwner owner) : super._empty(owner);
 
   void _update(Map map, bool mapIsRef) {
-    // Extract full properties.1
+    // Extract full properties.
     _upgradeCollection(map, isolate);
     super._update(map, mapIsRef);
 
@@ -2938,7 +2948,7 @@
     // Coerce absence to false.
     valueAsStringIsTruncated = map['valueAsStringIsTruncated'] == true;
     closureFunction = map['closureFunction'];
-    name = map['name'];
+    name = map['name']?.toString();
     length = map['length'];
     pattern = map['pattern'];
     typeClass = map['typeClass'];
@@ -2971,7 +2981,7 @@
     if (map['fields'] != null) {
       var fields = <BoundField>[];
       for (var f in map['fields']) {
-        fields.add(new BoundField(f['decl'], f['value']));
+        fields.add(new BoundField(f['decl'], f['name'], f['value']));
       }
       this.fields = fields;
     } else {
diff --git a/runtime/vm/object_service.cc b/runtime/vm/object_service.cc
index 053c1c7..b33d279 100644
--- a/runtime/vm/object_service.cc
+++ b/runtime/vm/object_service.cc
@@ -1128,7 +1128,7 @@
 
   Array& field_array = Array::Handle();
   Field& field = Field::Handle();
-  Instance& field_value = Instance::Handle();
+  Object& field_value = Object::Handle();
   {
     JSONArray jsarr(jsobj, "fields");
     for (intptr_t i = classes.length() - 1; i >= 0; i--) {
@@ -1137,7 +1137,7 @@
         for (intptr_t j = 0; j < field_array.Length(); j++) {
           field ^= field_array.At(j);
           if (!field.is_static()) {
-            field_value ^= GetField(field);
+            field_value = GetField(field);
             JSONObject jsfield(&jsarr);
             jsfield.AddProperty("type", "BoundField");
             jsfield.AddProperty("decl", field);
diff --git a/runtime/vm/service/service.md b/runtime/vm/service/service.md
index cc58737..8525ff0 100644
--- a/runtime/vm/service/service.md
+++ b/runtime/vm/service/service.md
@@ -3742,13 +3742,16 @@
   // An object that is part of a retaining path.
   @Object value;
 
-  // The offset of the retaining object in a containing list.
+  // If `value` is a List, `parentListIndex` is the index where the previous
+  // object on the retaining path is located.
   int parentListIndex [optional];
 
-  // The key mapping to the retaining object in a containing map.
+  // If `value` is a Map, `parentMapKey` is the key mapping to the previous
+  // object on the retaining path.
   @Object parentMapKey [optional];
 
-  // The name of the field containing the retaining object within an object.
+  // If `value` is a non-List, non-Map object, `parentField` is the name of the
+  // field containing the previous object on the retaining path.
   string parentField [optional];
 }
 ```
diff --git a/sdk/lib/ffi/dynamic_library.dart b/sdk/lib/ffi/dynamic_library.dart
index 5009ab6..e31b2d7 100644
--- a/sdk/lib/ffi/dynamic_library.dart
+++ b/sdk/lib/ffi/dynamic_library.dart
@@ -68,6 +68,11 @@
   /// Looks up a native function and returns it as a Dart function.
   ///
   /// [T] is the C function signature, and [F] is the Dart function signature.
+  ///
+  /// [isLeaf] specifies whether the function is a leaf function.
+  /// A leaf function must not run Dart code or call back into the Dart VM.
+  /// Leaf calls are faster than non-leaf calls.
+  ///
   /// For example:
   ///
   /// ```c
diff --git a/sdk/lib/ffi/ffi.dart b/sdk/lib/ffi/ffi.dart
index 2668e71..98a7909 100644
--- a/sdk/lib/ffi/ffi.dart
+++ b/sdk/lib/ffi/ffi.dart
@@ -155,6 +155,10 @@
     on Pointer<NativeFunction<NF>> {
   /// Convert to Dart function, automatically marshalling the arguments
   /// and return value.
+  ///
+  /// [isLeaf] specifies whether the function is a leaf function.
+  /// A leaf function must not run Dart code or call back into the Dart VM.
+  /// Leaf calls are faster than non-leaf calls.
   external DF asFunction<@DartRepresentationOf('NF') DF extends Function>(
       {bool isLeaf = false});
 }
@@ -876,7 +880,13 @@
 @Since('2.14')
 class FfiNative<T> {
   final String nativeName;
+
+  /// Specifies whether the function is a leaf function.
+  ///
+  /// A leaf function must not run Dart code or call back into the Dart VM.
+  /// Leaf calls are faster than non-leaf calls.
   final bool isLeaf;
+
   const FfiNative(this.nativeName, {this.isLeaf = false});
 }
 
diff --git a/tests/ffi/regress_47594_test.dart b/tests/ffi/regress_47594_test.dart
index abd6628..4af4974 100644
--- a/tests/ffi/regress_47594_test.dart
+++ b/tests/ffi/regress_47594_test.dart
@@ -6,7 +6,7 @@
 // FFI leaf calls did not mark the thread for the transition and would cause
 // the stack walker to segfault when it was unable to interpret the frame.
 //
-// VMOptions=--deterministic --enable-vm-service --profiler
+// VMOptions=--deterministic --enable-vm-service=0 --profiler
 
 import 'dart:ffi';
 
diff --git a/tests/ffi_2/regress_47594_test.dart b/tests/ffi_2/regress_47594_test.dart
index abd6628..4af4974 100644
--- a/tests/ffi_2/regress_47594_test.dart
+++ b/tests/ffi_2/regress_47594_test.dart
@@ -6,7 +6,7 @@
 // FFI leaf calls did not mark the thread for the transition and would cause
 // the stack walker to segfault when it was unable to interpret the frame.
 //
-// VMOptions=--deterministic --enable-vm-service --profiler
+// VMOptions=--deterministic --enable-vm-service=0 --profiler
 
 import 'dart:ffi';
 
diff --git a/tests/standalone/out_of_memory_slow_growth_test.dart b/tests/standalone/out_of_memory_slow_growth_test.dart
index e534dc6..b284dee 100644
--- a/tests/standalone/out_of_memory_slow_growth_test.dart
+++ b/tests/standalone/out_of_memory_slow_growth_test.dart
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 // VMOptions=--old_gen_heap_size=20
-// VMOptions=--old_gen_heap_size=20 --enable_vm_service --pause_isolates_on_unhandled_exceptions
+// VMOptions=--old_gen_heap_size=20 --enable_vm_service=0 --pause_isolates_on_unhandled_exceptions
 
 import "package:expect/expect.dart";
 
diff --git a/tests/standalone_2/out_of_memory_slow_growth_test.dart b/tests/standalone_2/out_of_memory_slow_growth_test.dart
index f624f10..ad9106c 100644
--- a/tests/standalone_2/out_of_memory_slow_growth_test.dart
+++ b/tests/standalone_2/out_of_memory_slow_growth_test.dart
@@ -5,7 +5,7 @@
 // @dart = 2.9
 
 // VMOptions=--old_gen_heap_size=20
-// VMOptions=--old_gen_heap_size=20 --enable_vm_service --pause_isolates_on_unhandled_exceptions
+// VMOptions=--old_gen_heap_size=20 --enable_vm_service=0 --pause_isolates_on_unhandled_exceptions
 
 import "package:expect/expect.dart";
 
diff --git a/tools/VERSION b/tools/VERSION
index 2382c3e..e36783b 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 19
 PATCH 0
-PRERELEASE 388
+PRERELEASE 389
 PRERELEASE_PATCH 0