Version 2.10.0-144.0.dev

Merge commit '9ad182a528c18753a2a0f887b0e36ef37013932e' into 'dev'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9b56ce5..98626d0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -54,8 +54,12 @@
 
 #### Linter
 
-Updated the Linter to `0.1.118`, which includes:
+Updated the Linter to `0.1.119`, which includes:
 
+* Fixed `close_sinks` to handle `this`-prefixed property accesses.
+* New lint: `unnecessary_null_checks`.
+* Fixed `unawaited_futures` to handle `Future` subtypes.
+* New lint: `avoid_type_to_string`.
 * New lint: `unnecessary_nullable_for_final_variable_declarations`.
 * Fixed NPE in `prefer_asserts_in_initializer_lists`.
 * Fixed range error in `unnecessary_string_escapes`.
diff --git a/DEPS b/DEPS
index 5271f55..43df7a6 100644
--- a/DEPS
+++ b/DEPS
@@ -44,7 +44,7 @@
   # co19 is a cipd package. Use update.sh in tests/co19[_2] to update these
   # hashes. It requires access to the dart-build-access group, which EngProd
   # has.
-  "co19_rev": "e8c8b7edab7df1a73c0215da7d458acd0f6c1ef3",
+  "co19_rev": "827f96b4cddf68cd12049ad5612c53e215b58fcf",
   "co19_2_rev": "e48b3090826cf40b8037648f19d211e8eab1b4b6",
 
   # The internal benchmarks to use. See go/dart-benchmarks-internal
@@ -113,7 +113,7 @@
   "intl_tag": "0.16.1",
   "jinja2_rev": "2222b31554f03e62600cd7e383376a7c187967a1",
   "json_rpc_2_rev": "8f189db8f0c299187a0e8fa959dba7e9b0254be5",
-  "linter_tag": "0.1.118",
+  "linter_tag": "0.1.119",
   "logging_rev": "9561ba016ae607747ae69b846c0e10958ca58ed4",
   "markupsafe_rev": "8f45f5cfa0009d2a70589bcda0349b8cb2b72783",
   "markdown_rev": "dbeafd47759e7dd0a167602153bb9c49fb5e5fe7",
diff --git a/pkg/analysis_server/lib/src/services/completion/postfix/postfix_completion.dart b/pkg/analysis_server/lib/src/services/completion/postfix/postfix_completion.dart
index 403fbb3..b648f7f 100644
--- a/pkg/analysis_server/lib/src/services/completion/postfix/postfix_completion.dart
+++ b/pkg/analysis_server/lib/src/services/completion/postfix/postfix_completion.dart
@@ -8,10 +8,12 @@
 import 'package:analyzer/dart/analysis/session.dart';
 import 'package:analyzer/dart/ast/ast.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/dart/element/type_provider.dart';
 import 'package:analyzer/dart/element/type_system.dart';
 import 'package:analyzer/src/dart/ast/utilities.dart';
+import 'package:analyzer/src/dart/element/type.dart';
 import 'package:analyzer/src/generated/java_core.dart';
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
@@ -444,7 +446,18 @@
     if (astNode is ThrowExpression) {
       var expr = astNode;
       var type = expr.expression.staticType;
-      return type.getDisplayString(withNullability: false);
+
+      // Only print nullability for non-legacy types in non-legacy libraries.
+      var showNullability = type.nullabilitySuffix != NullabilitySuffix.star &&
+          (astNode.root as CompilationUnit)
+              .declaredElement
+              .library
+              .isNonNullableByDefault;
+
+      // Can't catch nullable types, strip `?`s now that we've checked for `*`s.
+      return (type as TypeImpl)
+          .withNullability(NullabilitySuffix.none)
+          .getDisplayString(withNullability: showNullability);
     }
     return 'Exception';
   }
diff --git a/pkg/analysis_server/test/services/completion/postfix/postfix_completion_test.dart b/pkg/analysis_server/test/services/completion/postfix/postfix_completion_test.dart
index 44c2b39..07d36a2 100644
--- a/pkg/analysis_server/test/services/completion/postfix/postfix_completion_test.dart
+++ b/pkg/analysis_server/test/services/completion/postfix/postfix_completion_test.dart
@@ -4,6 +4,7 @@
 
 import 'package:analysis_server/src/protocol_server.dart';
 import 'package:analysis_server/src/services/completion/postfix/postfix_completion.dart';
+import 'package:analyzer/src/dart/analysis/experiments.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
@@ -708,6 +709,162 @@
 ''');
   }
 
+  Future<void> test_tryonThrowStatement_nnbd() async {
+    createAnalysisOptionsFile(experiments: [EnableString.non_nullable]);
+    await _prepareCompletion('.tryon', '''
+f() {
+  throw 'error';.tryon
+}
+''');
+    _assertHasChange('Expand .tryon', '''
+f() {
+  try {
+    throw 'error';/*caret*/
+  } on String catch (e, s) {
+    print(s);
+  }
+}
+''');
+  }
+
+  Future<void> test_tryonThrowStatement_nnbd_into_legacy() async {
+    createAnalysisOptionsFile(experiments: [EnableString.non_nullable]);
+    newFile('/home/test/lib/a.dart', content: r'''
+String? x;
+''');
+    await _prepareCompletion('.tryon', '''
+// @dart = 2.8
+import 'a.dart';
+f() {
+  throw x;.tryon
+}
+''');
+    _assertHasChange('Expand .tryon', '''
+// @dart = 2.8
+import 'a.dart';
+f() {
+  try {
+    throw x;/*caret*/
+  } on String catch (e, s) {
+    print(s);
+  }
+}
+''');
+  }
+
+  Future<void> test_tryonThrowStatement_nnbd_into_legacy_nested() async {
+    createAnalysisOptionsFile(experiments: [EnableString.non_nullable]);
+    newFile('/home/test/lib/a.dart', content: r'''
+List<String?> x;
+''');
+    await _prepareCompletion('.tryon', '''
+// @dart = 2.8
+import 'a.dart';
+f() {
+  throw x;.tryon
+}
+''');
+    _assertHasChange('Expand .tryon', '''
+// @dart = 2.8
+import 'a.dart';
+f() {
+  try {
+    throw x;/*caret*/
+  } on List<String> catch (e, s) {
+    print(s);
+  }
+}
+''');
+  }
+
+  Future<void> test_tryonThrowStatement_nnbd_legacy() async {
+    createAnalysisOptionsFile(experiments: [EnableString.non_nullable]);
+    newFile('/home/test/lib/a.dart', content: r'''
+// @dart = 2.8
+String x;
+''');
+    await _prepareCompletion('.tryon', '''
+import 'a.dart';
+f() {
+  throw x;.tryon
+}
+''');
+    _assertHasChange('Expand .tryon', '''
+import 'a.dart';
+f() {
+  try {
+    throw x;/*caret*/
+  } on String catch (e, s) {
+    print(s);
+  }
+}
+''');
+  }
+
+  Future<void> test_tryonThrowStatement_nnbd_legacy_nested() async {
+    createAnalysisOptionsFile(experiments: [EnableString.non_nullable]);
+    newFile('/home/test/lib/a.dart', content: r'''
+// @dart = 2.8
+List<String> x;
+''');
+    await _prepareCompletion('.tryon', '''
+import 'a.dart';
+f() {
+  throw x;.tryon
+}
+''');
+    _assertHasChange('Expand .tryon', '''
+import 'a.dart';
+f() {
+  try {
+    throw x;/*caret*/
+  } on List<String> catch (e, s) {
+    print(s);
+  }
+}
+''');
+  }
+
+  Future<void> test_tryonThrowStatement_nnbd_nullable() async {
+    createAnalysisOptionsFile(experiments: [EnableString.non_nullable]);
+    await _prepareCompletion('.tryon', '''
+f() {
+  String? x;
+  throw x;.tryon
+}
+''');
+    _assertHasChange('Expand .tryon', '''
+f() {
+  String? x;
+  try {
+    throw x;/*caret*/
+  } on String catch (e, s) {
+    print(s);
+  }
+}
+''');
+  }
+
+  Future<void> test_tryonThrowStatement_nnbd_nullable_nested() async {
+    createAnalysisOptionsFile(experiments: [EnableString.non_nullable]);
+    await _prepareCompletion('.tryon', '''
+f() {
+  List<String?>? x;
+  throw x;.tryon
+}
+''');
+    _assertHasChange('Expand .tryon', '''
+f() {
+  List<String?>? x;
+  try {
+    throw x;/*caret*/
+  } on List<String?> catch (e, s) {
+    print(s);
+  }
+}
+''');
+  }
+
   Future<void> test_tryonThrowString() async {
     await _prepareCompletion('.tryon', '''
 f() {
diff --git a/pkg/analyzer/lib/file_system/memory_file_system.dart b/pkg/analyzer/lib/file_system/memory_file_system.dart
index 091943f..a139b26 100644
--- a/pkg/analyzer/lib/file_system/memory_file_system.dart
+++ b/pkg/analyzer/lib/file_system/memory_file_system.dart
@@ -20,6 +20,7 @@
       HashMap<String, _MemoryResource>();
   final Map<String, Uint8List> _pathToBytes = HashMap<String, Uint8List>();
   final Map<String, int> _pathToTimestamp = HashMap<String, int>();
+  final Map<String, String> _pathToLinkedPath = {};
   final Map<String, List<StreamController<WatchEvent>>> _pathToWatchers =
       HashMap<String, List<StreamController<WatchEvent>>>();
   int nextStamp = 0;
@@ -176,6 +177,13 @@
     }
   }
 
+  /// Create a link from the [path] to the [target].
+  void newLink(String path, String target) {
+    _ensureAbsoluteAndNormalized(path);
+    _ensureAbsoluteAndNormalized(target);
+    _pathToLinkedPath[path] = target;
+  }
+
   File updateFile(String path, String content, [int stamp]) {
     _ensureAbsoluteAndNormalized(path);
     newFolder(pathContext.dirname(path));
@@ -276,6 +284,30 @@
     return newFile;
   }
 
+  String _resolveLinks(String path) {
+    var linkTarget = _pathToLinkedPath[path];
+    if (linkTarget != null) {
+      return linkTarget;
+    }
+
+    var parentPath = _pathContext.dirname(path);
+    if (parentPath == path) {
+      return path;
+    }
+
+    var canonicalParentPath = _resolveLinks(parentPath);
+
+    var baseName = _pathContext.basename(path);
+    var result = _pathContext.join(canonicalParentPath, baseName);
+
+    linkTarget = _pathToLinkedPath[result];
+    if (linkTarget != null) {
+      return linkTarget;
+    }
+
+    return result;
+  }
+
   void _setFileContent(_MemoryFile file, List<int> bytes) {
     String path = file.path;
     _pathToResource[path] = file;
@@ -370,7 +402,10 @@
       : super(provider, path);
 
   @override
-  bool get exists => provider._pathToResource[path] is _MemoryFile;
+  bool get exists {
+    var canonicalPath = provider._resolveLinks(path);
+    return provider._pathToResource[canonicalPath] is _MemoryFile;
+  }
 
   @override
   int get lengthSync {
@@ -379,7 +414,8 @@
 
   @override
   int get modificationStamp {
-    int stamp = provider._pathToTimestamp[path];
+    var canonicalPath = provider._resolveLinks(path);
+    int stamp = provider._pathToTimestamp[canonicalPath];
     if (stamp == null) {
       throw FileSystemException(path, 'File "$path" does not exist.');
     }
@@ -412,7 +448,8 @@
 
   @override
   Uint8List readAsBytesSync() {
-    Uint8List content = provider._pathToBytes[path];
+    var canonicalPath = provider._resolveLinks(path);
+    Uint8List content = provider._pathToBytes[canonicalPath];
     if (content == null) {
       throw FileSystemException(path, 'File "$path" does not exist.');
     }
@@ -421,7 +458,8 @@
 
   @override
   String readAsStringSync() {
-    Uint8List content = provider._pathToBytes[path];
+    var canonicalPath = provider._resolveLinks(path);
+    Uint8List content = provider._pathToBytes[canonicalPath];
     if (content == null) {
       throw FileSystemException(path, 'File "$path" does not exist.');
     }
@@ -434,7 +472,10 @@
   }
 
   @override
-  File resolveSymbolicLinksSync() => this;
+  File resolveSymbolicLinksSync() {
+    var canonicalPath = provider._resolveLinks(path);
+    return provider.getFile(canonicalPath);
+  }
 
   @override
   void writeAsBytesSync(List<int> bytes) {
@@ -538,7 +579,10 @@
   }
 
   @override
-  Folder resolveSymbolicLinksSync() => this;
+  Folder resolveSymbolicLinksSync() {
+    var canonicalPath = provider._resolveLinks(path);
+    return provider.getFolder(canonicalPath);
+  }
 
   @override
   Uri toUri() => provider.pathContext.toUri(path + '/');
diff --git a/pkg/analyzer/lib/src/dart/element/replacement_visitor.dart b/pkg/analyzer/lib/src/dart/element/replacement_visitor.dart
index c550ccd..d636bf2 100644
--- a/pkg/analyzer/lib/src/dart/element/replacement_visitor.dart
+++ b/pkg/analyzer/lib/src/dart/element/replacement_visitor.dart
@@ -25,6 +25,7 @@
 
   DartType createFunctionType({
     @required FunctionType type,
+    @required List<DartType> newTypeArguments,
     @required List<TypeParameterElement> newTypeParameters,
     @required List<ParameterElement> newParameters,
     @required DartType newReturnType,
@@ -41,6 +42,8 @@
       parameters: newParameters ?? type.parameters,
       returnType: newReturnType ?? type.returnType,
       nullabilitySuffix: newNullability ?? type.nullabilitySuffix,
+      element: type.element,
+      typeArguments: newTypeArguments ?? type.typeArguments,
     );
   }
 
@@ -160,6 +163,15 @@
 
     var newReturnType = visitType(node.returnType);
 
+    List<DartType> newTypeArguments;
+    for (var i = 0; i < node.typeArguments.length; i++) {
+      var substitution = node.typeArguments[i].accept(this);
+      if (substitution != null) {
+        newTypeArguments ??= node.typeArguments.toList(growable: false);
+        newTypeArguments[i] = substitution;
+      }
+    }
+
     changeVariance();
 
     List<ParameterElement> newParameters;
@@ -186,6 +198,7 @@
 
     return createFunctionType(
       type: node,
+      newTypeArguments: newTypeArguments,
       newTypeParameters: newTypeParameters,
       newParameters: newParameters,
       newReturnType: newReturnType,
diff --git a/pkg/analyzer/lib/src/error/best_practices_verifier.dart b/pkg/analyzer/lib/src/error/best_practices_verifier.dart
index 8809534..24c9c85 100644
--- a/pkg/analyzer/lib/src/error/best_practices_verifier.dart
+++ b/pkg/analyzer/lib/src/error/best_practices_verifier.dart
@@ -1892,7 +1892,7 @@
       case TargetKind.function:
         return 'top-level functions';
       case TargetKind.library:
-        return 'librarys';
+        return 'libraries';
       case TargetKind.getter:
         return 'getters';
       case TargetKind.method:
diff --git a/pkg/analyzer/lib/src/services/available_declarations.dart b/pkg/analyzer/lib/src/services/available_declarations.dart
index b7ea31c..b19999a 100644
--- a/pkg/analyzer/lib/src/services/available_declarations.dart
+++ b/pkg/analyzer/lib/src/services/available_declarations.dart
@@ -704,15 +704,26 @@
 
   _File _getFileByUri(DeclarationsContext context, Uri uri) {
     var file = _uriToFile[uri];
-    if (file == null) {
-      var path = context._resolveUri(uri);
-      if (path != null) {
-        file = _File(this, path, uri);
-        _pathToFile[path] = file;
-        _uriToFile[uri] = file;
-        file.refresh(context);
-      }
+    if (file != null) {
+      return file;
     }
+
+    var path = context._resolveUri(uri);
+    if (path == null) {
+      return null;
+    }
+
+    path = _resolveLinks(path);
+    file = _pathToFile[path];
+    if (file != null) {
+      return file;
+    }
+
+    file = _File(this, path, uri);
+    _pathToFile[path] = file;
+    _uriToFile[uri] = file;
+
+    file.refresh(context);
     return file;
   }
 
@@ -791,6 +802,13 @@
       LibraryChange._(changedLibraries, removedLibraries),
     );
   }
+
+  /// Return the [path] with resolved file system links.
+  String _resolveLinks(String path) {
+    var resource = _resourceProvider.getFile(path);
+    resource = resource.resolveSymbolicLinksSync();
+    return resource.path;
+  }
 }
 
 class Libraries {
diff --git a/pkg/analyzer/test/file_system/memory_file_system_test.dart b/pkg/analyzer/test/file_system/memory_file_system_test.dart
index 00d30bc..a353328 100644
--- a/pkg/analyzer/test/file_system/memory_file_system_test.dart
+++ b/pkg/analyzer/test/file_system/memory_file_system_test.dart
@@ -251,12 +251,16 @@
     expect(newFile.readAsStringSync(), defaultFileContent);
   }
 
-  @failingTest
   @override
   test_resolveSymbolicLinksSync_links_existing() {
-    // TODO(brianwilkerson) Decide how to test this given that we cannot
-    // create a link in a MemoryResourceProvider.
-    fail('Not tested');
+    var a = provider.convertPath('/test/lib/a.dart');
+    var b = provider.convertPath('/test/lib/b.dart');
+
+    provider.newLink(b, a);
+    provider.newFile(a, 'aaa');
+
+    var resolved = provider.getFile(b).resolveSymbolicLinksSync();
+    expect(resolved.path, a);
   }
 
   @override
@@ -286,7 +290,18 @@
 }
 
 @reflectiveTest
-class MemoryFolderTest extends BaseTest with FolderTestMixin {}
+class MemoryFolderTest extends BaseTest with FolderTestMixin {
+  test_resolveSymbolicLinksSync() {
+    var lib = provider.convertPath('/test/lib');
+    var foo = provider.convertPath('/test/lib/foo');
+
+    provider.newLink(foo, lib);
+    provider.newFolder(lib);
+
+    var resolved = provider.getFolder(foo).resolveSymbolicLinksSync();
+    expect(resolved.path, lib);
+  }
+}
 
 @reflectiveTest
 class MemoryResourceProviderTest extends BaseTest
@@ -369,6 +384,36 @@
     expect(() => provider.newFolder('not/absolute'), throwsArgumentError);
   }
 
+  test_newLink_folder() {
+    provider.newLink(
+      provider.convertPath('/test/lib/foo'),
+      provider.convertPath('/test/lib'),
+    );
+
+    provider.newFile(
+      provider.convertPath('/test/lib/a.dart'),
+      'aaa',
+    );
+
+    {
+      var path = '/test/lib/foo/a.dart';
+      var convertedPath = provider.convertPath(path);
+      var file = provider.getFile(convertedPath);
+      expect(file.exists, true);
+      expect(file.modificationStamp, isNonNegative);
+      expect(file.readAsStringSync(), 'aaa');
+    }
+
+    {
+      var path = '/test/lib/foo/foo/a.dart';
+      var convertedPath = provider.convertPath(path);
+      var file = provider.getFile(convertedPath);
+      expect(file.exists, true);
+      expect(file.modificationStamp, isNonNegative);
+      expect(file.readAsStringSync(), 'aaa');
+    }
+  }
+
   @override
   test_pathContext() {
     if (path.style == path.Style.windows) {
diff --git a/pkg/analyzer/test/src/dart/resolution/resolution.dart b/pkg/analyzer/test/src/dart/resolution/resolution.dart
index cb4d4f8..003a597 100644
--- a/pkg/analyzer/test/src/dart/resolution/resolution.dart
+++ b/pkg/analyzer/test/src/dart/resolution/resolution.dart
@@ -350,6 +350,17 @@
     assertType(node, type);
   }
 
+  /// We have a contract with the Angular team that FunctionType(s) from
+  /// typedefs carry the element of the typedef, and the type arguments.
+  void assertFunctionTypeTypedef(
+    FunctionType type, {
+    @required FunctionTypeAliasElement element,
+    @required List<String> typeArguments,
+  }) {
+    assertElement2(type.element, declaration: element.function);
+    assertElementTypeStrings(type.typeArguments, typeArguments);
+  }
+
   void assertHasTestErrors() {
     expect(result.errors, isNotEmpty);
   }
diff --git a/pkg/analyzer/test/src/dart/resolution/type_name_test.dart b/pkg/analyzer/test/src/dart/resolution/type_name_test.dart
index f50eea9..502c43d 100644
--- a/pkg/analyzer/test/src/dart/resolution/type_name_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/type_name_test.dart
@@ -382,10 +382,15 @@
 f(F a) {}
 ''');
 
-    assertTypeName(
-      findNode.typeName('F a'),
-      import_a.functionTypeAlias('F'),
-      'int* Function(bool*)*',
+    var element = import_a.functionTypeAlias('F');
+
+    var typeName = findNode.typeName('F a');
+    assertTypeName(typeName, element, 'int* Function(bool*)*');
+
+    assertFunctionTypeTypedef(
+      typeName.type,
+      element: element,
+      typeArguments: [],
     );
   }
 
@@ -401,10 +406,15 @@
 f(F a) {}
 ''');
 
-    assertTypeName(
-      findNode.typeName('F a'),
-      import_a.functionTypeAlias('F'),
-      'dynamic Function(bool*)*',
+    var element = import_a.functionTypeAlias('F');
+
+    var typeName = findNode.typeName('F a');
+    assertTypeName(typeName, element, 'dynamic Function(bool*)*');
+
+    assertFunctionTypeTypedef(
+      typeName.type,
+      element: element,
+      typeArguments: ['dynamic'],
     );
   }
 
@@ -420,10 +430,15 @@
 f(F a) {}
 ''');
 
-    assertTypeName(
-      findNode.typeName('F a'),
-      import_a.functionTypeAlias('F'),
-      'num* Function(bool*)*',
+    var element = import_a.functionTypeAlias('F');
+
+    var typeName = findNode.typeName('F a');
+    assertTypeName(typeName, element, 'num* Function(bool*)*');
+
+    assertFunctionTypeTypedef(
+      typeName.type,
+      element: element,
+      typeArguments: ['num*'],
     );
   }
 
@@ -439,10 +454,15 @@
 f(F<int> a) {}
 ''');
 
-    assertTypeName(
-      findNode.typeName('F<int> a'),
-      import_a.functionTypeAlias('F'),
-      'int* Function(bool*)*',
+    var element = import_a.functionTypeAlias('F');
+
+    var typeName = findNode.typeName('F<int> a');
+    assertTypeName(typeName, element, 'int* Function(bool*)*');
+
+    assertFunctionTypeTypedef(
+      typeName.type,
+      element: element,
+      typeArguments: ['int*'],
     );
   }
 
diff --git a/pkg/analyzer/test/src/services/available_declarations_test.dart b/pkg/analyzer/test/src/services/available_declarations_test.dart
index e21ccd8..0855c5d 100644
--- a/pkg/analyzer/test/src/services/available_declarations_test.dart
+++ b/pkg/analyzer/test/src/services/available_declarations_test.dart
@@ -201,6 +201,22 @@
     expect(library.uriStr, 'package:test/test.dart');
   }
 
+  test_getLibrary_exportViaRecursiveLink() async {
+    resourceProvider.newLink('/home/test/lib/foo', '/home/test/lib');
+
+    newFile('/home/test/lib/a.dart', content: r'''
+export 'foo/a.dart';
+class A {}
+''');
+    tracker.addContext(testAnalysisContext);
+
+    await _doAllTrackerWork();
+
+    var id = uriToLibrary['package:test/a.dart'].id;
+    var library = tracker.getLibrary(id);
+    expect(library.id, id);
+  }
+
   test_readByteStore() async {
     newFile('/home/test/lib/a.dart', content: r'''
 class A {}
diff --git a/pkg/compiler/lib/src/common_elements.dart b/pkg/compiler/lib/src/common_elements.dart
index 584fb51..7b7387c 100644
--- a/pkg/compiler/lib/src/common_elements.dart
+++ b/pkg/compiler/lib/src/common_elements.dart
@@ -441,6 +441,8 @@
 
   FunctionEntity get defineProperty;
 
+  FunctionEntity get throwLateInitializationError;
+
   bool isExtractTypeArguments(FunctionEntity member);
 
   ClassEntity getInstantiationClass(int typeArgumentCount);
@@ -1778,6 +1780,10 @@
   FunctionEntity get defineProperty => _findHelperFunction('defineProperty');
 
   @override
+  FunctionEntity get throwLateInitializationError =>
+      _findHelperFunction('throwLateInitializationError');
+
+  @override
   bool isExtractTypeArguments(FunctionEntity member) {
     return member.name == 'extractTypeArguments' &&
         member.library == internalLibrary;
diff --git a/pkg/compiler/lib/src/js_backend/backend_impact.dart b/pkg/compiler/lib/src/js_backend/backend_impact.dart
index cbb0bdd..383ed44 100644
--- a/pkg/compiler/lib/src/js_backend/backend_impact.dart
+++ b/pkg/compiler/lib/src/js_backend/backend_impact.dart
@@ -402,8 +402,10 @@
   BackendImpact _lazyField;
 
   BackendImpact get lazyField {
-    return _lazyField ??=
-        new BackendImpact(staticUses: [_commonElements.cyclicThrowHelper]);
+    return _lazyField ??= new BackendImpact(staticUses: [
+      _commonElements.cyclicThrowHelper,
+      _commonElements.throwLateInitializationError
+    ]);
   }
 
   BackendImpact _typeLiteral;
diff --git a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
index 4ec53ba..597f756 100644
--- a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
+++ b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
@@ -168,6 +168,27 @@
   };
 }
 
+// Creates a lazy final field that uses non-nullable initialization semantics.
+//
+// A lazy field has a storage entry, [name], which holds the value, and a
+// getter ([getterName]) to access the field. If the field wasn't set before
+// the first access, it is initialized with the [initializer].
+function lazyFinal(holder, name, getterName, initializer) {
+  var uninitializedSentinel = holder;
+  holder[name] = uninitializedSentinel;
+  holder[getterName] = function() {
+    if (holder[name] === uninitializedSentinel) {
+      var value = initializer();
+      if (holder[name] !== uninitializedSentinel) {
+        #throwLateInitializationError(name);
+      }
+      holder[name] = value;
+    }
+    holder[getterName] = function() { return this[name]; };
+    return holder[name];
+  };
+}
+
 // Given a list, marks it as constant.
 //
 // The runtime ensures that const-lists cannot be modified.
@@ -369,6 +390,7 @@
 
     makeConstList: makeConstList,
     lazy: lazy,
+    lazyFinal: lazyFinal,
     lazyOld: lazyOld,
     updateHolder: updateHolder,
     convertToFastObject: convertToFastObject,
@@ -686,6 +708,8 @@
       'directAccessTestExpression': js.js(_directAccessTestExpression),
       'cyclicThrow': _emitter
           .staticFunctionAccess(_closedWorld.commonElements.cyclicThrowHelper),
+      'throwLateInitializationError': _emitter.staticFunctionAccess(
+          _closedWorld.commonElements.throwLateInitializationError),
       'operatorIsPrefix': js.string(_namer.fixedNames.operatorIsPrefix),
       'tearOffCode': new js.Block(buildTearOffCode(
           _options, _emitter, _namer, _closedWorld.commonElements)),
@@ -1755,14 +1779,17 @@
     LocalAliases locals = LocalAliases();
     for (StaticField field in fields) {
       assert(field.holder.isStaticStateHolder);
+      String helper = field.usesNonNullableInitialization
+          ? field.isFinal
+              ? locals.find('_lazyFinal', 'hunkHelpers.lazyFinal')
+              : locals.find('_lazy', 'hunkHelpers.lazy')
+          : locals.find('_lazyOld', 'hunkHelpers.lazyOld');
       js.Statement statement = js.js.statement("#(#, #, #, #);", [
-        field.usesNonNullableInitialization
-            ? locals.find('_lazy', 'hunkHelpers.lazy')
-            : locals.find('_lazyOld', 'hunkHelpers.lazyOld'),
+        helper,
         field.holder.name,
         js.quoteName(field.name),
         js.quoteName(field.getterName),
-        field.code
+        field.code,
       ]);
 
       registerEntityAst(field.element, statement,
diff --git a/pkg/compiler/lib/src/ssa/builder_kernel.dart b/pkg/compiler/lib/src/ssa/builder_kernel.dart
index 23178f9..e2738fd 100644
--- a/pkg/compiler/lib/src/ssa/builder_kernel.dart
+++ b/pkg/compiler/lib/src/ssa/builder_kernel.dart
@@ -461,6 +461,9 @@
               registry.registerStaticUse(new StaticUse.staticInvoke(
                   closedWorld.commonElements.cyclicThrowHelper,
                   CallStructure.ONE_ARG));
+              registry.registerStaticUse(new StaticUse.staticInvoke(
+                  closedWorld.commonElements.throwLateInitializationError,
+                  CallStructure.ONE_ARG));
             }
             if (targetElement.isInstanceMember) {
               if (fieldData.isEffectivelyFinal ||
diff --git a/pkg/compiler/test/impact/data/invokes.dart b/pkg/compiler/test/impact/data/invokes.dart
index 634025b..a7e816f 100644
--- a/pkg/compiler/test/impact/data/invokes.dart
+++ b/pkg/compiler/test/impact/data/invokes.dart
@@ -588,7 +588,13 @@
 /*member: testTopLevelField:static=[topLevelField]*/
 testTopLevelField() => topLevelField;
 
-/*member: topLevelFieldLazy:static=[throwCyclicInit(1),topLevelFunction1(1)],type=[inst:JSNull]*/
+/*member: topLevelFieldLazy:
+ static=[
+  throwCyclicInit(1),
+  throwLateInitializationError(1),
+  topLevelFunction1(1)],
+ type=[inst:JSNull]
+*/
 var topLevelFieldLazy = topLevelFunction1(null);
 
 /*member: testTopLevelFieldLazy:static=[topLevelFieldLazy]*/
@@ -599,7 +605,13 @@
 /*member: testTopLevelFieldConst:type=[inst:JSNull]*/
 testTopLevelFieldConst() => topLevelFieldConst;
 
-/*member: topLevelFieldFinal:static=[throwCyclicInit(1),topLevelFunction1(1)],type=[inst:JSNull]*/
+/*member: topLevelFieldFinal:
+ static=[
+  throwCyclicInit(1),
+  throwLateInitializationError(1),
+  topLevelFunction1(1)],
+ type=[inst:JSNull]
+*/
 final topLevelFieldFinal = topLevelFunction1(null);
 
 /*member: testTopLevelFieldFinal:static=[topLevelFieldFinal]*/
diff --git a/runtime/vm/compiler/backend/il_test_helper.cc b/runtime/vm/compiler/backend/il_test_helper.cc
index 9c5788a..f4d002b 100644
--- a/runtime/vm/compiler/backend/il_test_helper.cc
+++ b/runtime/vm/compiler/backend/il_test_helper.cc
@@ -307,6 +307,13 @@
     }
   }
 
+  if (opcode == kMoveDebugStepChecks) {
+    while (cursor != nullptr && cursor->IsDebugStepCheck()) {
+      cursor = cursor->next();
+    }
+    return cursor;
+  }
+
   if (opcode == kMatchAndMoveGoto) {
     if (auto goto_instr = cursor->AsGoto()) {
       return goto_instr->successor();
@@ -362,6 +369,9 @@
   if (opcode == kMoveGlob) {
     return "kMoveGlob";
   }
+  if (opcode == kMoveDebugStepChecks) {
+    return "kMoveDebugStepChecks";
+  }
 
   switch (opcode) {
 #define EMIT_CASE(Instruction, _)                                              \
diff --git a/runtime/vm/compiler/backend/il_test_helper.h b/runtime/vm/compiler/backend/il_test_helper.h
index ec00bb7..01da0c3 100644
--- a/runtime/vm/compiler/backend/il_test_helper.h
+++ b/runtime/vm/compiler/backend/il_test_helper.h
@@ -122,6 +122,9 @@
   // Moves forward until the next match code matches.
   kMoveGlob,
 
+  // Moves over any DebugStepChecks.
+  kMoveDebugStepChecks,
+
   // Invalid match opcode used as default [insert_before] argument to TryMatch
   // to signal that no insertions should occur.
   kInvalidMatchOpCode,
diff --git a/runtime/vm/compiler/compiler_sources.gni b/runtime/vm/compiler/compiler_sources.gni
index 3c72125..f1c672a 100644
--- a/runtime/vm/compiler/compiler_sources.gni
+++ b/runtime/vm/compiler/compiler_sources.gni
@@ -187,6 +187,7 @@
   "backend/typed_data_aot_test.cc",
   "backend/yield_position_test.cc",
   "cha_test.cc",
+  "frontend/kernel_binary_flowgraph_test.cc",
   "write_barrier_elimination_test.cc",
 ]
 
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
index c2d252c..7b22ac1 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
@@ -3613,34 +3613,87 @@
          LoadLocal(parsed_function()->expression_temp_var());
 }
 
-Fragment StreamingFlowGraphBuilder::BuildStringConcatenation(TokenPosition* p) {
-  TokenPosition position = ReadPosition();  // read position.
-  if (p != NULL) *p = position;
+void StreamingFlowGraphBuilder::FlattenStringConcatenation(
+    PiecesCollector* collector) {
+  const auto length = ReadListLength();
+  for (intptr_t i = 0; i < length; ++i) {
+    const auto offset = reader_.offset();
+    switch (PeekTag()) {
+      case kStringLiteral: {
+        ReadTag();
+        const String& s = H.DartSymbolPlain(ReadStringReference());
+        // Skip empty strings.
+        if (!s.Equals("")) {
+          collector->Add({-1, &s});
+        }
+        break;
+      }
+      case kStringConcatenation: {
+        // Flatten by hoisting nested expressions up into the outer concat.
+        ReadTag();
+        ReadPosition();
+        FlattenStringConcatenation(collector);
+        break;
+      }
+      default: {
+        collector->Add({offset, nullptr});
+        SkipExpression();
+      }
+    }
+  }
+}
 
-  intptr_t length = ReadListLength();  // read list length.
-  // Note: there will be "length" expressions.
+Fragment StreamingFlowGraphBuilder::BuildStringConcatenation(TokenPosition* p) {
+  TokenPosition position = ReadPosition();
+  if (p != nullptr) {
+    *p = position;
+  }
+
+  // Collect and flatten all pieces of this and any nested StringConcats.
+  // The result is a single sequence of pieces, potentially flattened to
+  // a single String.
+  // The collector will hold concatenated strings and Reader offsets of
+  // non-string pieces.
+  PiecesCollector collector(Z, &H);
+  FlattenStringConcatenation(&collector);
+  collector.FlushRun();
+
+  if (collector.pieces.length() == 1) {
+    // No need to Interp. a single string, so return string as a Constant:
+    if (collector.pieces[0].literal != nullptr) {
+      return Constant(*collector.pieces[0].literal);
+    }
+    // A single non-string piece is handle by StringInterpolateSingle:
+    AlternativeReadingScope scope(&reader_, collector.pieces[0].offset);
+    Fragment instructions;
+    instructions += BuildExpression();
+    instructions += StringInterpolateSingle(position);
+    return instructions;
+  }
 
   Fragment instructions;
-  if (length == 1) {
-    instructions += BuildExpression();  // read expression.
-    instructions += StringInterpolateSingle(position);
-  } else {
-    // The type arguments for CreateArray.
-    instructions += Constant(TypeArguments::ZoneHandle(Z));
-    instructions += IntConstant(length);
-    instructions += CreateArray();
-    LocalVariable* array = MakeTemporary();
-
-    for (intptr_t i = 0; i < length; ++i) {
+  instructions += Constant(TypeArguments::ZoneHandle(Z));
+  instructions += IntConstant(collector.pieces.length());
+  instructions += CreateArray();
+  LocalVariable* array = MakeTemporary();
+  for (intptr_t i = 0; i < collector.pieces.length(); ++i) {
+    // All pieces are now either a concat'd string or an expression we can
+    // read at a given offset.
+    if (collector.pieces[i].literal != nullptr) {
       instructions += LoadLocal(array);
       instructions += IntConstant(i);
-      instructions += BuildExpression();  // read ith expression.
-      instructions += StoreIndexed(kArrayCid);
+      instructions += Constant(*collector.pieces[i].literal);
+    } else {
+      AlternativeReadingScope scope(&reader_, collector.pieces[i].offset);
+      instructions += LoadLocal(array);
+      instructions += IntConstant(i);
+      instructions += BuildExpression();
     }
-
-    instructions += StringInterpolate(position);
+    instructions += StoreIndexed(kArrayCid);
   }
 
+  instructions += StringInterpolate(position);
+
   return instructions;
 }
 
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
index f3a2fee..65ea0bd 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
@@ -366,6 +366,53 @@
   // Kernel buffer and pushes the resulting Function object.
   Fragment BuildFfiNativeCallbackFunction();
 
+  // Piece of a StringConcatenation.
+  // Represents either a StringLiteral, or a Reader offset to the expression.
+  struct ConcatPiece {
+    intptr_t offset;
+    const String* literal;
+  };
+
+  // Collector that automatically concatenates adjacent string ConcatPieces.
+  struct PiecesCollector {
+    explicit PiecesCollector(Zone* z, TranslationHelper* translation_helper)
+        : pieces(5),
+          literal_run(z, 1),
+          translation_helper(translation_helper) {}
+
+    GrowableArray<ConcatPiece> pieces;
+    GrowableHandlePtrArray<const String> literal_run;
+    TranslationHelper* translation_helper;
+
+    void Add(const ConcatPiece& piece) {
+      if (piece.literal != nullptr) {
+        literal_run.Add(*piece.literal);
+      } else {
+        FlushRun();
+        pieces.Add(piece);
+      }
+    }
+
+    void FlushRun() {
+      switch (literal_run.length()) {
+        case 0:
+          return;
+        case 1:
+          pieces.Add({-1, &literal_run[0]});
+          break;
+        default:
+          pieces.Add({-1, &translation_helper->DartString(literal_run)});
+      }
+      literal_run.Clear();
+    }
+  };
+
+  // Flattens and collects pieces of StringConcatenations such that:
+  //   ["a", "", "b"] => ["ab"]
+  //   ["a", StringConcat("b", "c")] => ["abc"]
+  //   ["a", "", StringConcat("b", my_var), "c"] => ["ab", my_var, "c"]
+  void FlattenStringConcatenation(PiecesCollector* collector);
+
   FlowGraphBuilder* flow_graph_builder_;
   ActiveClass* const active_class_;
   ConstantReader constant_reader_;
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph_test.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph_test.cc
new file mode 100644
index 0000000..80ddd91
--- /dev/null
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph_test.cc
@@ -0,0 +1,250 @@
+// Copyright (c) 2020, 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.
+
+#include "vm/compiler/frontend/kernel_binary_flowgraph.h"
+
+#include "vm/compiler/backend/il_test_helper.h"
+#include "vm/object.h"
+#include "vm/unit_test.h"
+
+namespace dart {
+
+ISOLATE_UNIT_TEST_CASE(StreamingFlowGraphBuilder_ConstFoldStringConcats) {
+  // According to the Dart spec:
+  // "Adjacent strings are implicitly concatenated to form a single string
+  // literal."
+  const char* kScript = R"(
+    test() {
+      var s = 'aaaa'
+          'bbbb'
+          'cccc';
+      return s;
+    }
+  )";
+
+  const auto& root_library = Library::Handle(LoadTestScript(kScript));
+  const auto& function = Function::Handle(GetFunction(root_library, "test"));
+
+  Invoke(root_library, "test");
+
+  TestPipeline pipeline(function, CompilerPass::kJIT);
+  FlowGraph* flow_graph = pipeline.RunPasses({
+      CompilerPass::kComputeSSA,
+  });
+
+  auto entry = flow_graph->graph_entry()->normal_entry();
+  EXPECT(entry != nullptr);
+
+  ReturnInstr* ret = nullptr;
+
+  ILMatcher cursor(flow_graph, entry);
+  // clang-format off
+  RELEASE_ASSERT(cursor.TryMatch({
+    kMatchAndMoveFunctionEntry,
+    kMatchAndMoveCheckStackOverflow,
+    kMoveDebugStepChecks,
+    {kMatchReturn, &ret},
+  }));
+  // clang-format on
+
+  EXPECT(ret->value()->BindsToConstant());
+  EXPECT(ret->value()->BoundConstant().IsString());
+  const String& ret_str = String::Cast(ret->value()->BoundConstant());
+  EXPECT(ret_str.Equals("aaaabbbbcccc"));
+}
+
+ISOLATE_UNIT_TEST_CASE(StreamingFlowGraphBuilder_FlattenNestedStringInterp) {
+  // We should collapse nested StringInterpolates:
+  const char* kScript = R"(
+    test(String s) {
+      return '$s' '${'d' 'e'}';
+    }
+    main() => test('u');
+  )";
+
+  const auto& root_library = Library::Handle(LoadTestScript(kScript));
+  const auto& function = Function::Handle(GetFunction(root_library, "test"));
+
+  Invoke(root_library, "main");
+
+  TestPipeline pipeline(function, CompilerPass::kJIT);
+  FlowGraph* flow_graph = pipeline.RunPasses({
+      CompilerPass::kComputeSSA,
+  });
+
+  auto entry = flow_graph->graph_entry()->normal_entry();
+  EXPECT(entry != nullptr);
+
+  StoreIndexedInstr* store1 = nullptr;
+  StoreIndexedInstr* store2 = nullptr;
+
+  ILMatcher cursor(flow_graph, entry);
+  // clang-format off
+  RELEASE_ASSERT(cursor.TryMatch({
+    kMatchAndMoveFunctionEntry,
+    kMatchAndMoveCheckStackOverflow,
+    kMoveDebugStepChecks,
+    kMatchAndMoveCreateArray,
+    {kMatchAndMoveStoreIndexed, &store1},
+    {kMatchAndMoveStoreIndexed, &store2},
+    kMatchAndMoveStringInterpolate,
+    kMoveDebugStepChecks,
+    kMatchReturn,
+  }));
+  // clang-format on
+
+  // StoreIndexed(tmp_array, 0, s)
+  EXPECT(store1->index()->BindsToConstant());
+  EXPECT(store1->index()->BoundConstant().IsInteger());
+  EXPECT(Integer::Cast(store1->index()->BoundConstant()).AsInt64Value() == 0);
+
+  EXPECT(!store1->value()->BindsToConstant());
+
+  // StoreIndexed(tmp_array, 1, "de")
+  EXPECT(store2->index()->BindsToConstant());
+  EXPECT(store2->index()->BoundConstant().IsInteger());
+  EXPECT(Integer::Cast(store2->index()->BoundConstant()).AsInt64Value() == 1);
+
+  EXPECT(store2->value()->BindsToConstant());
+  EXPECT(store2->value()->BoundConstant().IsString());
+  EXPECT(String::Cast(store2->value()->BoundConstant()).Equals("de"));
+}
+
+ISOLATE_UNIT_TEST_CASE(StreamingFlowGraphBuilder_DropEmptyStringInterp) {
+  // We should drop empty strings from StringInterpolates:
+  const char* kScript = R"(
+    test(s) {
+      return '' 'a' '$s' '' 'b' '';
+    }
+    main() => test('u');
+  )";
+
+  const auto& root_library = Library::Handle(LoadTestScript(kScript));
+  const auto& function = Function::Handle(GetFunction(root_library, "test"));
+
+  Invoke(root_library, "main");
+
+  TestPipeline pipeline(function, CompilerPass::kJIT);
+  FlowGraph* flow_graph = pipeline.RunPasses({
+      CompilerPass::kComputeSSA,
+  });
+
+  auto entry = flow_graph->graph_entry()->normal_entry();
+  EXPECT(entry != nullptr);
+
+  StoreIndexedInstr* store1 = nullptr;
+  StoreIndexedInstr* store2 = nullptr;
+  StoreIndexedInstr* store3 = nullptr;
+
+  ILMatcher cursor(flow_graph, entry);
+  // clang-format off
+  RELEASE_ASSERT(cursor.TryMatch({
+    kMatchAndMoveFunctionEntry,
+    kMatchAndMoveCheckStackOverflow,
+    kMoveDebugStepChecks,
+    kMatchAndMoveCreateArray,
+    {kMatchAndMoveStoreIndexed, &store1},
+    {kMatchAndMoveStoreIndexed, &store2},
+    {kMatchAndMoveStoreIndexed, &store3},
+    kMatchAndMoveStringInterpolate,
+    kMoveDebugStepChecks,
+    kMatchReturn,
+  }));
+  // clang-format on
+
+  // StoreIndexed(tmp_array, 0, "ab")
+  EXPECT(store1->index()->BindsToConstant());
+  EXPECT(store1->index()->BoundConstant().IsInteger());
+  EXPECT(Integer::Cast(store1->index()->BoundConstant()).AsInt64Value() == 0);
+
+  EXPECT(store1->value()->BindsToConstant());
+  EXPECT(store1->value()->BoundConstant().IsString());
+  EXPECT(String::Cast(store1->value()->BoundConstant()).Equals("a"));
+
+  // StoreIndexed(tmp_array, 1, s)
+  EXPECT(store2->index()->BindsToConstant());
+  EXPECT(store2->index()->BoundConstant().IsInteger());
+  EXPECT(Integer::Cast(store2->index()->BoundConstant()).AsInt64Value() == 1);
+
+  EXPECT(!store2->value()->BindsToConstant());
+
+  // StoreIndexed(tmp_array, 2, "b")
+  EXPECT(store3->index()->BindsToConstant());
+  EXPECT(store3->index()->BoundConstant().IsInteger());
+  EXPECT(Integer::Cast(store3->index()->BoundConstant()).AsInt64Value() == 2);
+
+  EXPECT(store3->value()->BindsToConstant());
+  EXPECT(store3->value()->BoundConstant().IsString());
+  EXPECT(String::Cast(store3->value()->BoundConstant()).Equals("b"));
+}
+
+ISOLATE_UNIT_TEST_CASE(StreamingFlowGraphBuilder_ConcatStringLits) {
+  // We should drop empty strings from StringInterpolates:
+  const char* kScript = R"(
+    test(s) {
+      return '' 'a' '' 'b' '$s' '' 'c' '' 'd' '';
+    }
+    main() => test('u');
+  )";
+
+  const auto& root_library = Library::Handle(LoadTestScript(kScript));
+  const auto& function = Function::Handle(GetFunction(root_library, "test"));
+
+  Invoke(root_library, "main");
+
+  TestPipeline pipeline(function, CompilerPass::kJIT);
+  FlowGraph* flow_graph = pipeline.RunPasses({
+      CompilerPass::kComputeSSA,
+  });
+
+  auto entry = flow_graph->graph_entry()->normal_entry();
+  EXPECT(entry != nullptr);
+
+  StoreIndexedInstr* store1 = nullptr;
+  StoreIndexedInstr* store2 = nullptr;
+  StoreIndexedInstr* store3 = nullptr;
+
+  ILMatcher cursor(flow_graph, entry);
+  // clang-format off
+  RELEASE_ASSERT(cursor.TryMatch({
+    kMatchAndMoveFunctionEntry,
+    kMatchAndMoveCheckStackOverflow,
+    kMoveDebugStepChecks,
+    kMatchAndMoveCreateArray,
+    {kMatchAndMoveStoreIndexed, &store1},
+    {kMatchAndMoveStoreIndexed, &store2},
+    {kMatchAndMoveStoreIndexed, &store3},
+    kMatchAndMoveStringInterpolate,
+    kMoveDebugStepChecks,
+    kMatchReturn,
+  }));
+  // clang-format on
+
+  // StoreIndexed(tmp_array, 0, "ab")
+  EXPECT(store1->index()->BindsToConstant());
+  EXPECT(store1->index()->BoundConstant().IsInteger());
+  EXPECT(Integer::Cast(store1->index()->BoundConstant()).AsInt64Value() == 0);
+
+  EXPECT(store1->value()->BindsToConstant());
+  EXPECT(store1->value()->BoundConstant().IsString());
+  EXPECT(String::Cast(store1->value()->BoundConstant()).Equals("ab"));
+
+  // StoreIndexed(tmp_array, 1, s)
+  EXPECT(store2->index()->BindsToConstant());
+  EXPECT(store2->index()->BoundConstant().IsInteger());
+  EXPECT(Integer::Cast(store2->index()->BoundConstant()).AsInt64Value() == 1);
+
+  EXPECT(!store2->value()->BindsToConstant());
+
+  // StoreIndexed(tmp_array, 2, "cd")
+  EXPECT(store3->index()->BindsToConstant());
+  EXPECT(store3->index()->BoundConstant().IsInteger());
+  EXPECT(Integer::Cast(store3->index()->BoundConstant()).AsInt64Value() == 2);
+
+  EXPECT(store3->value()->BindsToConstant());
+  EXPECT(store3->value()->BoundConstant().IsString());
+  EXPECT(String::Cast(store3->value()->BoundConstant()).Equals("cd"));
+}
+
+}  // namespace dart
diff --git a/runtime/vm/compiler/frontend/kernel_translation_helper.cc b/runtime/vm/compiler/frontend/kernel_translation_helper.cc
index d70966c..3c2b977 100644
--- a/runtime/vm/compiler/frontend/kernel_translation_helper.cc
+++ b/runtime/vm/compiler/frontend/kernel_translation_helper.cc
@@ -370,6 +370,11 @@
   return String::ZoneHandle(Z, String::FromUTF8(utf8_array, len, space));
 }
 
+const String& TranslationHelper::DartString(
+    const GrowableHandlePtrArray<const String>& pieces) {
+  return String::ZoneHandle(Z, Symbols::FromConcatAll(thread_, pieces));
+}
+
 const String& TranslationHelper::DartSymbolPlain(const char* content) const {
   return String::ZoneHandle(Z, Symbols::New(thread_, content));
 }
diff --git a/runtime/vm/compiler/frontend/kernel_translation_helper.h b/runtime/vm/compiler/frontend/kernel_translation_helper.h
index f05d261..cec43d5 100644
--- a/runtime/vm/compiler/frontend/kernel_translation_helper.h
+++ b/runtime/vm/compiler/frontend/kernel_translation_helper.h
@@ -129,6 +129,8 @@
                      intptr_t len,
                      Heap::Space space);
 
+  const String& DartString(const GrowableHandlePtrArray<const String>& pieces);
+
   const String& DartSymbolPlain(const char* content) const;
   String& DartSymbolPlain(StringIndex string_index) const;
   const String& DartSymbolObfuscate(const char* content) const;
diff --git a/runtime/vm/growable_array.h b/runtime/vm/growable_array.h
index 5cd2d0b..85bfedc 100644
--- a/runtime/vm/growable_array.h
+++ b/runtime/vm/growable_array.h
@@ -66,6 +66,8 @@
 
   intptr_t length() const { return array_.length(); }
 
+  void Clear() { array_.Clear(); }
+
   const GrowableArray<T*>& growable_array() const { return array_; }
 
  private:
diff --git a/sdk/lib/_internal/js_runtime/lib/js_helper.dart b/sdk/lib/_internal/js_runtime/lib/js_helper.dart
index ea0239f..542f18b 100644
--- a/sdk/lib/_internal/js_runtime/lib/js_helper.dart
+++ b/sdk/lib/_internal/js_runtime/lib/js_helper.dart
@@ -48,6 +48,7 @@
 import 'dart:_internal'
     show
         EfficientLengthIterable,
+        LateInitializationErrorImpl,
         MappedIterable,
         IterableElementError,
         SubListIterable;
@@ -3023,3 +3024,7 @@
 
 const kRequiredSentinel = const _Required();
 bool isRequired(Object? value) => identical(kRequiredSentinel, value);
+
+/// Called by generated code to throw a LateInitializationError.
+void throwLateInitializationError(String name) =>
+    throw LateInitializationErrorImpl(name);
diff --git a/tools/VERSION b/tools/VERSION
index b4d2ba5..0010549 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 10
 PATCH 0
-PRERELEASE 143
+PRERELEASE 144
 PRERELEASE_PATCH 0
\ No newline at end of file