Version 2.18.0-4.0.dev

Merge commit 'd2d810a8e8a87a584bfcfa90f63d484e895847bf' into 'dev'
diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json
index 765df26..66edc3a 100644
--- a/.dart_tool/package_config.json
+++ b/.dart_tool/package_config.json
@@ -463,7 +463,7 @@
       "name": "modular_test",
       "rootUri": "../pkg/modular_test",
       "packageUri": "lib/",
-      "languageVersion": "2.2"
+      "languageVersion": "2.10"
     },
     {
       "name": "native_stack_traces",
diff --git a/pkg/compiler/lib/src/kernel/transformations/async_lowering.dart b/pkg/compiler/lib/src/kernel/transformations/async_lowering.dart
index 8f60070..1f5e31b 100644
--- a/pkg/compiler/lib/src/kernel/transformations/async_lowering.dart
+++ b/pkg/compiler/lib/src/kernel/transformations/async_lowering.dart
@@ -11,6 +11,7 @@
 class _FunctionData {
   final List<AwaitExpression> awaits = [];
   final Set<ReturnStatement> returnStatements = {};
+  bool hasAsyncLoop = false;
 
   _FunctionData();
 
@@ -34,7 +35,7 @@
   AsyncLowering(this._coreTypes);
 
   bool _shouldTryAsyncLowering(FunctionNode node) =>
-      node.asyncMarker == AsyncMarker.Async;
+      node.asyncMarker == AsyncMarker.Async && !_functions.last.hasAsyncLoop;
 
   void enterFunction(FunctionNode node) {
     _functions.add(_FunctionData());
@@ -134,4 +135,10 @@
   void visitReturnStatement(ReturnStatement statement) {
     _functions.last.returnStatements.add(statement);
   }
+
+  void visitForInStatement(ForInStatement statement) {
+    if (statement.isAsync && _functions.isNotEmpty) {
+      _functions.last.hasAsyncLoop = true;
+    }
+  }
 }
diff --git a/pkg/compiler/lib/src/kernel/transformations/lowering.dart b/pkg/compiler/lib/src/kernel/transformations/lowering.dart
index ba289ae..b90f526 100644
--- a/pkg/compiler/lib/src/kernel/transformations/lowering.dart
+++ b/pkg/compiler/lib/src/kernel/transformations/lowering.dart
@@ -107,4 +107,11 @@
     statement.transformChildren(this);
     return statement;
   }
+
+  @override
+  TreeNode visitForInStatement(ForInStatement statement) {
+    _asyncLowering?.visitForInStatement(statement);
+    statement.transformChildren(this);
+    return statement;
+  }
 }
diff --git a/pkg/compiler/pubspec.yaml b/pkg/compiler/pubspec.yaml
index ba75211..e9e7fd7 100644
--- a/pkg/compiler/pubspec.yaml
+++ b/pkg/compiler/pubspec.yaml
@@ -35,7 +35,6 @@
   http: any
   js:
     path: ../js
-  package_config: any
   path: any
   source_maps: any
   # Unpublished packages that can be used via path dependency
diff --git a/pkg/compiler/tool/modular_test_suite_helper.dart b/pkg/compiler/tool/modular_test_suite_helper.dart
index b85aeb5..98985b9 100644
--- a/pkg/compiler/tool/modular_test_suite_helper.dart
+++ b/pkg/compiler/tool/modular_test_suite_helper.dart
@@ -14,11 +14,11 @@
 import 'package:compiler/src/kernel/dart2js_target.dart';
 import 'package:front_end/src/compute_platform_binaries_location.dart'
     show computePlatformBinariesLocation;
+import 'package:modular_test/src/create_package_config.dart';
 import 'package:modular_test/src/io_pipeline.dart';
 import 'package:modular_test/src/pipeline.dart';
-import 'package:modular_test/src/suite.dart';
 import 'package:modular_test/src/runner.dart';
-import 'package:package_config/package_config.dart';
+import 'package:modular_test/src/suite.dart';
 
 String packageConfigJsonPath = ".dart_tool/package_config.json";
 Uri sdkRoot = Platform.script.resolve("../../../");
@@ -42,17 +42,6 @@
 const txtId = DataId("txt");
 const fakeRoot = 'dev-dart-app:/';
 
-String _packageConfigEntry(String name, Uri root,
-    {Uri packageRoot, LanguageVersion version}) {
-  var fields = [
-    '"name": "${name}"',
-    '"rootUri": "$root"',
-    if (packageRoot != null) '"packageUri": "$packageRoot"',
-    if (version != null) '"languageVersion": "$version"'
-  ];
-  return '{${fields.join(',')}}';
-}
-
 String getRootScheme(Module module) {
   // We use non file-URI schemes for representeing source locations in a
   // root-agnostic way. This allows us to refer to file across modules and
@@ -81,63 +70,6 @@
   return module.sources.map((uri) => sourceToImportUri(module, uri)).toList();
 }
 
-void writePackageConfig(
-    Module module, Set<Module> transitiveDependencies, Uri root) async {
-  // TODO(joshualitt): Figure out a way to support package configs in
-  // tests/modular.
-  var packageConfig = await loadPackageConfigUri(packageConfigUri);
-
-  // We create both a .packages and package_config.json file which defines
-  // the location of this module if it is a package.  The CFE requires that
-  // if a `package:` URI of a dependency is used in an import, then we need
-  // that package entry in the associated file. However, after it checks that
-  // the definition exists, the CFE will not actually use the resolved URI if
-  // a library for the import URI is already found in one of the provide
-  // .dill files of the dependencies. For that reason, and to ensure that
-  // a step only has access to the files provided in a module, we generate a
-  // config file with invalid folders for other packages.
-  // TODO(sigmund): follow up with the CFE to see if we can remove the need
-  // for these dummy entries..
-  // TODO(joshualitt): Generate just the json file.
-  var packagesJson = [];
-  var packagesContents = StringBuffer();
-  if (module.isPackage) {
-    packagesContents.write('${module.name}:${module.packageBase}\n');
-    packagesJson.add(_packageConfigEntry(
-        module.name, Uri.parse('../${module.packageBase}')));
-  }
-
-  int unusedNum = 0;
-  for (Module dependency in transitiveDependencies) {
-    if (dependency.isPackage) {
-      // rootUri should be ignored for dependent modules, so we pass in a
-      // bogus value.
-      var rootUri = Uri.parse('unused$unusedNum');
-      unusedNum++;
-
-      var dependentPackage = packageConfig[dependency.name];
-      var packageJson = dependentPackage == null
-          ? _packageConfigEntry(dependency.name, rootUri)
-          : _packageConfigEntry(dependentPackage.name, rootUri,
-              version: dependentPackage.languageVersion);
-      packagesJson.add(packageJson);
-      packagesContents.write('${dependency.name}:$rootUri\n');
-    }
-  }
-
-  if (module.isPackage) {
-    await File.fromUri(root.resolve(packageConfigJsonPath))
-        .create(recursive: true);
-    await File.fromUri(root.resolve(packageConfigJsonPath)).writeAsString('{'
-        '  "configVersion": ${packageConfig.version},'
-        '  "packages": [ ${packagesJson.join(',')} ]'
-        '}');
-  }
-
-  await File.fromUri(root.resolve('.packages'))
-      .writeAsString('$packagesContents');
-}
-
 abstract class CFEStep extends IOModularStep {
   final String stepName;
 
@@ -158,11 +90,14 @@
     if (_options.verbose) print("\nstep: $stepName on $module");
 
     Set<Module> transitiveDependencies = computeTransitiveDependencies(module);
-    writePackageConfig(module, transitiveDependencies, root);
+    await writePackageConfig(module, transitiveDependencies, root);
 
     String rootScheme = getRootScheme(module);
     List<String> sources;
-    List<String> extraArgs = ['--packages-file', '$rootScheme:/.packages'];
+    List<String> extraArgs = [
+      '--packages-file',
+      '$rootScheme:/$packageConfigJsonPath'
+    ];
     if (module.isSdk) {
       // When no flags are passed, we can skip compilation and reuse the
       // platform.dill created by build.py.
@@ -318,21 +253,21 @@
     List<String> sources = [];
     List<String> extraArgs = [];
     if (!module.isSdk) {
-      writePackageConfig(module, transitiveDependencies, root);
+      await writePackageConfig(module, transitiveDependencies, root);
       String rootScheme = getRootScheme(module);
       sources = getSources(module);
       dillDependencies = transitiveDependencies
           .map((m) => '${toUri(m, dillSummaryId)}')
           .toList();
       extraArgs = [
-        '--packages=${root.resolve('.packages')}',
+        '--packages=${root.resolve(packageConfigJsonPath)}',
         '--multi-root=$root',
         '--multi-root-scheme=$rootScheme',
       ];
     }
 
     List<String> args = [
-      '--packages=${sdkRoot.toFilePath()}/.packages',
+      '--packages=${sdkRoot.toFilePath()}/$packageConfigJsonPath',
       _dart2jsScript,
       '--no-sound-null-safety',
       if (_options.useSdk) '--libraries-spec=$_librarySpecForSnapshot',
@@ -400,7 +335,7 @@
         .toList();
     dataDependencies.add('${toUri(module, modularDataId)}');
     List<String> args = [
-      '--packages=${sdkRoot.toFilePath()}/.packages',
+      '--packages=${sdkRoot.toFilePath()}/$packageConfigJsonPath',
       _dart2jsScript,
       // TODO(sigmund): remove this dependency on libraries.json
       if (_options.useSdk) '--libraries-spec=$_librarySpecForSnapshot',
@@ -461,7 +396,7 @@
         .toList();
     dataDependencies.add('${toUri(module, modularDataId)}');
     List<String> args = [
-      '--packages=${sdkRoot.toFilePath()}/.packages',
+      '--packages=${sdkRoot.toFilePath()}/$packageConfigJsonPath',
       _dart2jsScript,
       // TODO(sigmund): remove this dependency on libraries.json
       if (_options.useSdk) '--libraries-spec=$_librarySpecForSnapshot',
@@ -508,7 +443,7 @@
       List<String> flags) async {
     if (_options.verbose) print("\nstep: dart2js global analysis on $module");
     List<String> args = [
-      '--packages=${sdkRoot.toFilePath()}/.packages',
+      '--packages=${sdkRoot.toFilePath()}/$packageConfigJsonPath',
       _dart2jsScript,
       // TODO(sigmund): remove this dependency on libraries.json
       if (_options.useSdk) '--libraries-spec=$_librarySpecForSnapshot',
@@ -562,7 +497,7 @@
       List<String> flags) async {
     if (_options.verbose) print("\nstep: dart2js backend on $module");
     List<String> args = [
-      '--packages=${sdkRoot.toFilePath()}/.packages',
+      '--packages=${sdkRoot.toFilePath()}/$packageConfigJsonPath',
       _dart2jsScript,
       if (_options.useSdk) '--libraries-spec=$_librarySpecForSnapshot',
       '${Flags.entryUri}=$fakeRoot${module.mainSource}',
@@ -615,7 +550,7 @@
       List<String> flags) async {
     if (_options.verbose) print("step: dart2js backend on $module");
     List<String> args = [
-      '--packages=${sdkRoot.toFilePath()}/.packages',
+      '--packages=${sdkRoot.toFilePath()}/$packageConfigJsonPath',
       _dart2jsScript,
       if (_options.useSdk) '--libraries-spec=$_librarySpecForSnapshot',
       '${Flags.entryUri}=$fakeRoot${module.mainSource}',
diff --git a/pkg/dev_compiler/pubspec.yaml b/pkg/dev_compiler/pubspec.yaml
index f84be03..90c1a69 100644
--- a/pkg/dev_compiler/pubspec.yaml
+++ b/pkg/dev_compiler/pubspec.yaml
@@ -33,7 +33,6 @@
   lints: any
   modular_test:
     path: ../modular_test
-  package_config: any
   sourcemap_testing:
     path: ../sourcemap_testing
   stack_trace: any
diff --git a/pkg/dev_compiler/test/modular_suite.dart b/pkg/dev_compiler/test/modular_suite.dart
index eb34432..1d540ba 100644
--- a/pkg/dev_compiler/test/modular_suite.dart
+++ b/pkg/dev_compiler/test/modular_suite.dart
@@ -9,11 +9,11 @@
 /// This is a shell that runs multiple tests, one per folder under `data/`.
 import 'dart:io';
 
+import 'package:modular_test/src/create_package_config.dart';
 import 'package:modular_test/src/io_pipeline.dart';
 import 'package:modular_test/src/pipeline.dart';
 import 'package:modular_test/src/runner.dart';
 import 'package:modular_test/src/suite.dart';
-import 'package:package_config/package_config.dart';
 
 String packageConfigJsonPath = '.dart_tool/package_config.json';
 Uri sdkRoot = Platform.script.resolve('../../../');
@@ -22,13 +22,8 @@
 String _dartdevcScript;
 String _kernelWorkerScript;
 
-// TODO(joshualitt): Figure out a way to support package configs in
-// tests/modular.
-PackageConfig _packageConfig;
-
 void main(List<String> args) async {
   _options = Options.parse(args);
-  _packageConfig = await loadPackageConfigUri(packageConfigUri);
   await _resolveScripts();
   await runSuite(
       sdkRoot.resolve('tests/modular/'),
@@ -45,17 +40,6 @@
 const jsId = DataId('js');
 const txtId = DataId('txt');
 
-String _packageConfigEntry(String name, Uri root,
-    {Uri packageRoot, LanguageVersion version}) {
-  var fields = [
-    '"name": "$name"',
-    '"rootUri": "$root"',
-    if (packageRoot != null) '"packageUri": "$packageRoot"',
-    if (version != null) '"languageVersion": "$version"'
-  ];
-  return '{${fields.join(',')}}';
-}
-
 class SourceToSummaryDillStep implements IOModularStep {
   @override
   List<DataId> get resultData => const [dillId];
@@ -96,7 +80,7 @@
         _sourceToImportUri(module, rootScheme, relativeUri);
 
     var transitiveDependencies = computeTransitiveDependencies(module);
-    await _createPackagesFile(module, root, transitiveDependencies);
+    await writePackageConfig(module, transitiveDependencies, root);
 
     List<String> sources;
     List<String> extraArgs;
@@ -180,7 +164,7 @@
     if (_options.verbose) print('\nstep: ddc on $module');
 
     var transitiveDependencies = computeTransitiveDependencies(module);
-    await _createPackagesFile(module, root, transitiveDependencies);
+    await writePackageConfig(module, transitiveDependencies, root);
 
     var rootScheme = module.isSdk ? 'dev-dart-sdk' : 'dev-dart-app';
     List<String> sources;
@@ -333,57 +317,6 @@
   throw UnsupportedError('Unsupported platform.');
 }
 
-Future<void> _createPackagesFile(
-    Module module, Uri root, Set<Module> transitiveDependencies) async {
-  // We create both a .packages and package_config.json file which defines
-  // the location of this module if it is a package.  The CFE requires that
-  // if a `package:` URI of a dependency is used in an import, then we need
-  // that package entry in the associated file. However, after it checks that
-  // the definition exists, the CFE will not actually use the resolved URI if
-  // a library for the import URI is already found in one of the provide
-  // .dill files of the dependencies. For that reason, and to ensure that
-  // a step only has access to the files provided in a module, we generate a
-  // config file with invalid folders for other packages.
-  // TODO(sigmund): follow up with the CFE to see if we can remove the need
-  // for these dummy entries..
-  // TODO(joshualitt): Generate just the json file.
-  var packagesJson = [];
-  var packagesContents = StringBuffer();
-  if (module.isPackage) {
-    packagesContents.write('${module.name}:${module.packageBase}\n');
-    packagesJson.add(_packageConfigEntry(
-        module.name, Uri.parse('../${module.packageBase}')));
-  }
-  var unusedNum = 0;
-  for (var dependency in transitiveDependencies) {
-    if (dependency.isPackage) {
-      // rootUri should be ignored for dependent modules, so we pass in a
-      // bogus value.
-      var rootUri = Uri.parse('unused$unusedNum');
-      unusedNum++;
-      var dependentPackage = _packageConfig[dependency.name];
-      var packageJson = dependentPackage == null
-          ? _packageConfigEntry(dependency.name, rootUri)
-          : _packageConfigEntry(dependentPackage.name, rootUri,
-              version: dependentPackage.languageVersion);
-      packagesJson.add(packageJson);
-      packagesContents.write('${dependency.name}:$rootUri\n');
-    }
-  }
-
-  if (module.isPackage) {
-    await File.fromUri(root.resolve(packageConfigJsonPath))
-        .create(recursive: true);
-    await File.fromUri(root.resolve(packageConfigJsonPath)).writeAsString('{'
-        '  "configVersion": ${_packageConfig.version},'
-        '  "packages": [ ${packagesJson.join(',')} ]'
-        '}');
-  }
-
-  await File.fromUri(root.resolve('.packages'))
-      .writeAsString('$packagesContents');
-}
-
 String _sourceToImportUri(Module module, String rootScheme, Uri relativeUri) {
   if (module.isPackage) {
     var basePath = module.packageBase.path;
diff --git a/pkg/dev_compiler/test/modular_suite_nnbd.dart b/pkg/dev_compiler/test/modular_suite_nnbd.dart
index 4a1f559..4a19cfb 100644
--- a/pkg/dev_compiler/test/modular_suite_nnbd.dart
+++ b/pkg/dev_compiler/test/modular_suite_nnbd.dart
@@ -9,11 +9,11 @@
 /// This is a shell that runs multiple tests, one per folder under `data/`.
 import 'dart:io';
 
+import 'package:modular_test/src/create_package_config.dart';
 import 'package:modular_test/src/io_pipeline.dart';
 import 'package:modular_test/src/pipeline.dart';
 import 'package:modular_test/src/runner.dart';
 import 'package:modular_test/src/suite.dart';
-import 'package:package_config/package_config.dart';
 
 String packageConfigJsonPath = '.dart_tool/package_config.json';
 Uri sdkRoot = Platform.script.resolve('../../../');
@@ -22,13 +22,8 @@
 String _dartdevcScript;
 String _kernelWorkerScript;
 
-// TODO(joshualitt): Figure out a way to support package configs in
-// tests/modular.
-PackageConfig _packageConfig;
-
 void main(List<String> args) async {
   _options = Options.parse(args);
-  _packageConfig = await loadPackageConfigUri(packageConfigUri);
   await _resolveScripts();
   await runSuite(
       sdkRoot.resolve('tests/modular/'),
@@ -45,17 +40,6 @@
 const jsId = DataId('js');
 const txtId = DataId('txt');
 
-String _packageConfigEntry(String name, Uri root,
-    {Uri packageRoot, LanguageVersion version}) {
-  var fields = [
-    '"name": "$name"',
-    '"rootUri": "$root"',
-    if (packageRoot != null) '"packageUri": "$packageRoot"',
-    if (version != null) '"languageVersion": "$version"'
-  ];
-  return '{${fields.join(',')}}';
-}
-
 class SourceToSummaryDillStep implements IOModularStep {
   @override
   List<DataId> get resultData => const [dillId];
@@ -96,7 +80,7 @@
         _sourceToImportUri(module, rootScheme, relativeUri);
 
     var transitiveDependencies = computeTransitiveDependencies(module);
-    await _createPackagesFile(module, root, transitiveDependencies);
+    await writePackageConfig(module, transitiveDependencies, root);
 
     List<String> sources;
     List<String> extraArgs;
@@ -182,7 +166,7 @@
     if (_options.verbose) print('\nstep: ddc on $module');
 
     var transitiveDependencies = computeTransitiveDependencies(module);
-    await _createPackagesFile(module, root, transitiveDependencies);
+    await writePackageConfig(module, transitiveDependencies, root);
 
     var rootScheme = module.isSdk ? 'dev-dart-sdk' : 'dev-dart-app';
     List<String> sources;
@@ -337,57 +321,6 @@
   throw UnsupportedError('Unsupported platform.');
 }
 
-Future<void> _createPackagesFile(
-    Module module, Uri root, Set<Module> transitiveDependencies) async {
-  // We create both a .packages and package_config.json file which defines
-  // the location of this module if it is a package.  The CFE requires that
-  // if a `package:` URI of a dependency is used in an import, then we need
-  // that package entry in the associated file. However, after it checks that
-  // the definition exists, the CFE will not actually use the resolved URI if
-  // a library for the import URI is already found in one of the provide
-  // .dill files of the dependencies. For that reason, and to ensure that
-  // a step only has access to the files provided in a module, we generate a
-  // config file with invalid folders for other packages.
-  // TODO(sigmund): follow up with the CFE to see if we can remove the need
-  // for these dummy entries..
-  // TODO(joshualitt): Generate just the json file.
-  var packagesJson = [];
-  var packagesContents = StringBuffer();
-  if (module.isPackage) {
-    packagesContents.write('${module.name}:${module.packageBase}\n');
-    packagesJson.add(_packageConfigEntry(
-        module.name, Uri.parse('../${module.packageBase}')));
-  }
-  var unusedNum = 0;
-  for (var dependency in transitiveDependencies) {
-    if (dependency.isPackage) {
-      // rootUri should be ignored for dependent modules, so we pass in a
-      // bogus value.
-      var rootUri = Uri.parse('unused$unusedNum');
-      unusedNum++;
-      var dependentPackage = _packageConfig[dependency.name];
-      var packageJson = dependentPackage == null
-          ? _packageConfigEntry(dependency.name, rootUri)
-          : _packageConfigEntry(dependentPackage.name, rootUri,
-              version: dependentPackage.languageVersion);
-      packagesJson.add(packageJson);
-      packagesContents.write('${dependency.name}:$rootUri\n');
-    }
-  }
-
-  if (module.isPackage) {
-    await File.fromUri(root.resolve(packageConfigJsonPath))
-        .create(recursive: true);
-    await File.fromUri(root.resolve(packageConfigJsonPath)).writeAsString('{'
-        '  "configVersion": ${_packageConfig.version},'
-        '  "packages": [ ${packagesJson.join(',')} ]'
-        '}');
-  }
-
-  await File.fromUri(root.resolve('.packages'))
-      .writeAsString('$packagesContents');
-}
-
 String _sourceToImportUri(Module module, String rootScheme, Uri relativeUri) {
   if (module.isPackage) {
     var basePath = module.packageBase.path;
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart
index bbef654..e19523f 100644
--- a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart
@@ -8,7 +8,14 @@
   return (await 6) + 3;
 }
 
+Future<void> foo3() async {
+  await for (final x in Stream.empty()) {
+    break;
+  }
+}
+
 void main() {
   foo1();
   foo2();
+  foo3();
 }
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.strong.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.strong.expect
index eb616ff..f3422b1 100644
--- a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.strong.expect
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.strong.expect
@@ -9,7 +9,14 @@
 static method foo2() → asy::Future<core::int> async /* futureValueType= core::int */ {
   return (await 6).{core::num::+}(3){(core::num) → core::int};
 }
+static method foo3() → asy::Future<void> async /* futureValueType= void */ {
+  #L1:
+  await for (final dynamic x in new asy::_EmptyStream::•<dynamic>()) {
+    break #L1;
+  }
+}
 static method main() → void {
   self::foo1();
   self::foo2();
+  self::foo3();
 }
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.strong.transformed.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.strong.transformed.expect
index eb616ff..f3422b1 100644
--- a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.strong.transformed.expect
@@ -9,7 +9,14 @@
 static method foo2() → asy::Future<core::int> async /* futureValueType= core::int */ {
   return (await 6).{core::num::+}(3){(core::num) → core::int};
 }
+static method foo3() → asy::Future<void> async /* futureValueType= void */ {
+  #L1:
+  await for (final dynamic x in new asy::_EmptyStream::•<dynamic>()) {
+    break #L1;
+  }
+}
 static method main() → void {
   self::foo1();
   self::foo2();
+  self::foo3();
 }
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.textual_outline.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.textual_outline.expect
index 06a5c0d..c9ebdd9 100644
--- a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.textual_outline.expect
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.textual_outline.expect
@@ -1,3 +1,4 @@
 Future<void> foo1() async {}
 Future<int> foo2() async {}
+Future<void> foo3() async {}
 void main() {}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.textual_outline_modelled.expect
index 4ab99ee..4ab2b8a 100644
--- a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.textual_outline_modelled.expect
@@ -1,3 +1,4 @@
 Future<int> foo2() async {}
 Future<void> foo1() async {}
+Future<void> foo3() async {}
 void main() {}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.expect
index eb616ff..f3422b1 100644
--- a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.expect
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.expect
@@ -9,7 +9,14 @@
 static method foo2() → asy::Future<core::int> async /* futureValueType= core::int */ {
   return (await 6).{core::num::+}(3){(core::num) → core::int};
 }
+static method foo3() → asy::Future<void> async /* futureValueType= void */ {
+  #L1:
+  await for (final dynamic x in new asy::_EmptyStream::•<dynamic>()) {
+    break #L1;
+  }
+}
 static method main() → void {
   self::foo1();
   self::foo2();
+  self::foo3();
 }
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.modular.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.modular.expect
index eb616ff..f3422b1 100644
--- a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.modular.expect
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.modular.expect
@@ -9,7 +9,14 @@
 static method foo2() → asy::Future<core::int> async /* futureValueType= core::int */ {
   return (await 6).{core::num::+}(3){(core::num) → core::int};
 }
+static method foo3() → asy::Future<void> async /* futureValueType= void */ {
+  #L1:
+  await for (final dynamic x in new asy::_EmptyStream::•<dynamic>()) {
+    break #L1;
+  }
+}
 static method main() → void {
   self::foo1();
   self::foo2();
+  self::foo3();
 }
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.outline.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.outline.expect
index 85b14b9..0c508d4 100644
--- a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.outline.expect
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.outline.expect
@@ -7,5 +7,7 @@
   ;
 static method foo2() → asy::Future<core::int> async 
   ;
+static method foo3() → asy::Future<void> async 
+  ;
 static method main() → void
   ;
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.transformed.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.transformed.expect
index eb616ff..f3422b1 100644
--- a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.transformed.expect
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.transformed.expect
@@ -9,7 +9,14 @@
 static method foo2() → asy::Future<core::int> async /* futureValueType= core::int */ {
   return (await 6).{core::num::+}(3){(core::num) → core::int};
 }
+static method foo3() → asy::Future<void> async /* futureValueType= void */ {
+  #L1:
+  await for (final dynamic x in new asy::_EmptyStream::•<dynamic>()) {
+    break #L1;
+  }
+}
 static method main() → void {
   self::foo1();
   self::foo2();
+  self::foo3();
 }
diff --git a/pkg/modular_test/lib/src/create_package_config.dart b/pkg/modular_test/lib/src/create_package_config.dart
new file mode 100644
index 0000000..c25ef69
--- /dev/null
+++ b/pkg/modular_test/lib/src/create_package_config.dart
@@ -0,0 +1,71 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:package_config/package_config.dart';
+
+import 'find_sdk_root.dart';
+import 'suite.dart';
+
+Future<void> writePackageConfig(
+    Module module, Set<Module> transitiveDependencies, Uri root) async {
+  const packageConfigJsonPath = ".dart_tool/package_config.json";
+  var sdkRoot = await findRoot();
+  Uri packageConfigUri = sdkRoot.resolve(packageConfigJsonPath);
+  var packageConfig = await loadPackageConfigUri(packageConfigUri);
+
+  // We create a package_config.json file to support the CFE in (a) determine
+  // the default nullability if the current module is a package, and (b)
+  // resolve `package:` imports. Technically the latter shouldn't be necessary,
+  // but the CFE requires that if a `package:` URI of a dependency is used in an
+  // import, then we need that package entry in the associated file. In fact,
+  // after it checks that the definition exists, the CFE will not actually use
+  // the resolved URI if a library for the import URI is already found in one of
+  // the provide .dill files of the dependencies. For that reason, and to ensure
+  // that a step only has access to the files provided in a module, we generate
+  // a config file with invalid folders for other packages.
+  // TODO(sigmund): follow up with the CFE to see if we can remove the need
+  // for these dummy entries.
+  var packagesJson = [];
+  if (module.isPackage) {
+    packagesJson.add(_packageConfigEntry(
+        module.name, Uri.parse('../${module.packageBase}')));
+  }
+
+  int unusedNum = 0;
+  for (Module dependency in transitiveDependencies) {
+    if (dependency.isPackage) {
+      // rootUri should be ignored for dependent modules, so we pass in a
+      // bogus value.
+      var rootUri = Uri.parse('unused$unusedNum');
+      unusedNum++;
+
+      var dependentPackage = packageConfig[dependency.name];
+      var packageJson = dependentPackage == null
+          ? _packageConfigEntry(dependency.name, rootUri)
+          : _packageConfigEntry(dependentPackage.name, rootUri,
+              version: dependentPackage.languageVersion);
+      packagesJson.add(packageJson);
+    }
+  }
+
+  await File.fromUri(root.resolve(packageConfigJsonPath))
+      .create(recursive: true);
+  await File.fromUri(root.resolve(packageConfigJsonPath)).writeAsString('{'
+      '  "configVersion": ${packageConfig.version},'
+      '  "packages": [ ${packagesJson.join(',')} ]'
+      '}');
+}
+
+String _packageConfigEntry(String name, Uri root,
+    {Uri packageRoot, LanguageVersion version}) {
+  var fields = [
+    '"name": "${name}"',
+    '"rootUri": "$root"',
+    if (packageRoot != null) '"packageUri": "$packageRoot"',
+    if (version != null) '"languageVersion": "$version"'
+  ];
+  return '{${fields.join(',')}}';
+}
diff --git a/pkg/modular_test/lib/src/loader.dart b/pkg/modular_test/lib/src/loader.dart
index 825e679..b67f011 100644
--- a/pkg/modular_test/lib/src/loader.dart
+++ b/pkg/modular_test/lib/src/loader.dart
@@ -10,23 +10,13 @@
 ///   * individual .dart files, each file is considered a module. A
 ///   `main.dart` file is required as the entry point of the test.
 ///   * subfolders: each considered a module with multiple files
-///   * (optional) a .packages file:
-///       * if this is not specified, the test will use [defaultPackagesInput]
-///       instead.
-///       * if specified, it will be extended with the definitions in
-///       [defaultPackagesInput]. The list of packages provided is expected to
-///       be disjoint with those in [defaultPackagesInput].
 ///   * a modules.yaml file: a specification of dependencies between modules.
 ///     The format is described in `test_specification_parser.dart`.
 import 'dart:io';
-import 'dart:convert';
-import 'dart:typed_data';
 import 'suite.dart';
 import 'test_specification_parser.dart';
 import 'find_sdk_root.dart';
 
-import 'package:package_config/src/packages_file.dart' as packages_file;
-
 /// Returns the [ModularTest] associated with a folder under [uri].
 ///
 /// After scanning the contents of the folder, this method creates a
@@ -38,13 +28,12 @@
   var folder = Directory.fromUri(uri);
   var testUri = folder.uri; // normalized in case the trailing '/' was missing.
   Uri root = await findRoot();
-  Map<String, Uri> defaultPackages =
-      _parseDotPackagesBytesToLibMap(_defaultPackagesInput, root);
+  final defaultTestSpecification = parseTestSpecification(_defaultPackagesSpec);
+  Set<String> defaultPackages = defaultTestSpecification.packages.keys.toSet();
   Module sdkModule = await _createSdkModule(root);
   Map<String, Module> modules = {'sdk': sdkModule};
   String specString;
   Module mainModule;
-  Map<String, Uri> packages = {};
   var entries = folder.listSync(recursive: false).toList()
     // Sort to avoid dependency on file system order.
     ..sort(_compareFileSystemEntity);
@@ -59,7 +48,7 @@
               "'$moduleName' which conflicts with the sdk module "
               "that is provided by default.");
         }
-        if (defaultPackages.containsKey(moduleName)) {
+        if (defaultPackages.contains(moduleName)) {
           return _invalidTest("The file '$fileName' defines a module called "
               "'$moduleName' which conflicts with a package by the same name "
               "that is provided by default.");
@@ -75,9 +64,6 @@
             packageBase: Uri.parse('.'));
         if (isMain) mainModule = module;
         modules[moduleName] = module;
-      } else if (fileName == '.packages') {
-        List<int> packagesBytes = await entry.readAsBytes();
-        packages = _parseDotPackagesBytesToLibMap(packagesBytes, entryUri);
       } else if (fileName == 'modules.yaml') {
         specString = await entry.readAsString();
       }
@@ -90,7 +76,7 @@
             "which conflicts with the sdk module "
             "that is provided by default.");
       }
-      if (defaultPackages.containsKey(moduleName)) {
+      if (defaultPackages.contains(moduleName)) {
         return _invalidTest("The folder '$moduleName' defines a module "
             "which conflicts with a package by the same name "
             "that is provided by default.");
@@ -110,12 +96,17 @@
     return _invalidTest("main module is missing");
   }
 
-  _addDefaultPackageEntries(packages, defaultPackages);
-  await _addModulePerPackage(packages, modules);
   TestSpecification spec = parseTestSpecification(specString);
+  for (final name in defaultPackages) {
+    if (spec.packages.containsKey(name)) {
+      _invalidTest(
+          ".packages file defines a conflicting entry for package '$name'.");
+    }
+  }
+  await _addModulePerPackage(defaultTestSpecification.packages, root, modules);
+  await _addModulePerPackage(spec.packages, testUri, modules);
   _attachDependencies(spec.dependencies, modules);
-  _attachDependencies(
-      parseTestSpecification(_defaultPackagesSpec).dependencies, modules);
+  _attachDependencies(defaultTestSpecification.dependencies, modules);
   _addSdkDependencies(modules, sdkModule);
   _detectCyclesAndRemoveUnreachable(modules, mainModule);
   var sortedModules = modules.values.toList()
@@ -170,33 +161,21 @@
   }
 }
 
-void _addDefaultPackageEntries(
-    Map<String, Uri> packages, Map<String, Uri> defaultPackages) {
-  for (var name in defaultPackages.keys) {
-    var existing = packages[name];
-    if (existing != null && existing != defaultPackages[name]) {
-      _invalidTest(
-          ".packages file defines an conflicting entry for package '$name'.");
-    }
-    packages[name] = defaultPackages[name];
-  }
-}
-
 /// Create a module for each package dependency.
-Future<void> _addModulePerPackage(
-    Map<String, Uri> packages, Map<String, Module> modules) async {
+Future<void> _addModulePerPackage(Map<String, String> packages, Uri configRoot,
+    Map<String, Module> modules) async {
   for (var packageName in packages.keys) {
     var module = modules[packageName];
     if (module != null) {
       module.isPackage = true;
     } else {
-      var packageLibUri = packages[packageName];
-      var rootUri = Directory.fromUri(packageLibUri).parent.uri;
+      var packageLibUri = configRoot.resolve(packages[packageName]);
+      var packageRootUri = Directory.fromUri(packageLibUri).parent.uri;
       var sources = await _listModuleSources(packageLibUri);
       // TODO(sigmund): validate that we don't use a different alias for a
       // module that is part of the test (package name and module name should
       // match).
-      modules[packageName] = Module(packageName, [], rootUri, sources,
+      modules[packageName] = Module(packageName, [], packageRootUri, sources,
           isPackage: true, packageBase: Uri.parse('lib/'), isShared: true);
     }
   }
@@ -249,15 +228,6 @@
   toRemove.forEach(modules.remove);
 }
 
-/// Default entries for a .packages file with paths relative to the SDK root.
-List<int> _defaultPackagesInput = utf8.encode('''
-expect:pkg/expect/lib
-smith:pkg/smith/lib
-async_helper:pkg/async_helper/lib
-meta:pkg/meta/lib
-collection:third_party/pkg/collection/lib
-''');
-
 /// Specifies the dependencies of all packages in [_defaultPackagesInput]. This
 /// string needs to be updated if dependencies between those packages changes
 /// (which is rare).
@@ -270,6 +240,12 @@
   meta: []
   async_helper: []
   collection: []
+packages:
+  expect: pkg/expect/lib
+  smith: pkg/smith/lib
+  async_helper: pkg/async_helper/lib
+  meta: pkg/meta/lib
+  collection: third_party/pkg/collection/lib
 ''';
 
 /// Report an conflict error.
@@ -316,15 +292,3 @@
     }
   }
 }
-
-/// Parse [bytes] representing a `.packages` file into the map of package names
-/// to URIs of their `lib` locations.
-Map<String, Uri> _parseDotPackagesBytesToLibMap(Uint8List bytes, Uri baseUri) {
-  var map = <String, Uri>{};
-  var packageConfig =
-      packages_file.parse(bytes, baseUri, (error) => throw error);
-  for (var package in packageConfig.packages) {
-    map[package.name] = package.packageUriRoot;
-  }
-  return map;
-}
diff --git a/pkg/modular_test/lib/src/test_specification_parser.dart b/pkg/modular_test/lib/src/test_specification_parser.dart
index d0eed3d..85272b0 100644
--- a/pkg/modular_test/lib/src/test_specification_parser.dart
+++ b/pkg/modular_test/lib/src/test_specification_parser.dart
@@ -10,9 +10,16 @@
 ///      main: [b, expect]
 ///    flags:
 ///      - constant-update-2018
+///    packages:
+///      c: .
+///      a: a
 ///
-/// Where the dependencies section describe how modules depend on one another,
-/// and the flags section show what flags are needed to run that specific test.
+///
+/// Where:
+///   - the dependencies section describe how modules depend on one another,
+///   - the flags section show what flags are needed to run that specific test,
+///   - the packages section is used to create a package structure on top of the
+///     declared modules.
 ///
 /// When defining dependencies:
 ///   - Each name corresponds to a module.
@@ -21,9 +28,24 @@
 ///   - If a module has a single dependency, it can be written as a single
 ///     value.
 ///
+/// When defining packages:
+///   - The name corresponds to a package name, this doesn't need to match
+///     the name of the module. That said, it's common for some modules
+///     and packages to share their name (especially for the default set of
+///     packages included by the framework, like package:expect).
+///   - The value is a path to the folder containing the libraries of that
+///     package.
+///
+/// The packages entry is optional.  If this is not specified, the test will
+/// still have a default set of packages, like package:expect and package:meta.
+/// If the packages entry is specified, it will be extended with the definitions
+/// of the default set of packages as well. Thus, the list of packages provided
+/// is expected to be disjoint with those in the default set. The default set is
+/// defined directly in the code of `loader.dart`.
+///
 /// The logic in this library mostly treats these names as strings, separately
-/// `loader.dart` is responsible for validating and attaching this dependency
-/// information to a set of module definitions.
+/// `loader.dart` is responsible for validating, attaching dependency
+/// information to a set of module definitions, and resolving package paths.
 ///
 /// The framework is agnostic of what the flags are, but at this time we only
 /// use the name of experimental language features. These are then used to
@@ -76,7 +98,18 @@
     _invalidSpecification(
         "flags: '$flags' expected to be string or list of strings");
   }
-  return new TestSpecification(normalizedFlags, normalizedMap);
+
+  Map<String, String> normalizedPackages = {};
+  final packages = spec['packages'];
+  if (packages != null) {
+    if (packages is Map) {
+      normalizedPackages.addAll(packages.cast<String, String>());
+    } else {
+      _invalidSpecification("packages is not a map");
+    }
+  }
+  return new TestSpecification(
+      normalizedFlags, normalizedMap, normalizedPackages);
 }
 
 /// Data specifying details about a modular test including dependencies and
@@ -96,7 +129,13 @@
   /// (for instance, the module of `package:expect` or the sdk itself).
   final Map<String, List<String>> dependencies;
 
-  TestSpecification(this.flags, this.dependencies);
+  /// Map of package name to a relative path.
+  ///
+  /// The paths in this map are meant to be resolved relative to the location
+  /// where this test specification was defined.
+  final Map<String, String> packages;
+
+  TestSpecification(this.flags, this.dependencies, this.packages);
 }
 
 _invalidSpecification(String message) {
diff --git a/pkg/modular_test/pubspec.yaml b/pkg/modular_test/pubspec.yaml
index 68974ee..c1507d6 100644
--- a/pkg/modular_test/pubspec.yaml
+++ b/pkg/modular_test/pubspec.yaml
@@ -6,7 +6,7 @@
  This is used within the Dart SDK to define and validate modular tests, and to
  execute them using the modular pipeline of different SDK tools.
 environment:
-  sdk: ">=2.2.1 <3.0.0"
+  sdk: ">=2.10.0 <3.0.0"
 
 dependencies:
   args: any
diff --git a/pkg/modular_test/test/loader/dag_with_packages/.packages b/pkg/modular_test/test/loader/dag_with_packages/.packages
deleted file mode 100644
index 194aba9..0000000
--- a/pkg/modular_test/test/loader/dag_with_packages/.packages
+++ /dev/null
@@ -1,4 +0,0 @@
-a:a/
-b:b/
-d:d/
-c:c/
diff --git a/pkg/modular_test/test/loader/dag_with_packages/modules.yaml b/pkg/modular_test/test/loader/dag_with_packages/modules.yaml
index 0a775d2..fe1a957 100644
--- a/pkg/modular_test/test/loader/dag_with_packages/modules.yaml
+++ b/pkg/modular_test/test/loader/dag_with_packages/modules.yaml
@@ -2,3 +2,8 @@
   a: [b, c]
   b: d
   main: [a, b]
+packages:
+  a: a/
+  b: b/
+  d: d/
+  c: c/
diff --git a/pkg/modular_test/test/loader/invalid_packages_error/.packages b/pkg/modular_test/test/loader/invalid_packages_error/.packages
deleted file mode 100644
index d8892ce..0000000
--- a/pkg/modular_test/test/loader/invalid_packages_error/.packages
+++ /dev/null
@@ -1 +0,0 @@
-expect:.
diff --git a/pkg/modular_test/test/loader/invalid_packages_error/expectation.txt b/pkg/modular_test/test/loader/invalid_packages_error/expectation.txt
index 6931c77..b4e0bcf 100644
--- a/pkg/modular_test/test/loader/invalid_packages_error/expectation.txt
+++ b/pkg/modular_test/test/loader/invalid_packages_error/expectation.txt
@@ -1,3 +1,3 @@
 # This expectation file is generated by loader_test.dart
 
-Invalid test: .packages file defines an conflicting entry for package 'expect'.
\ No newline at end of file
+Invalid test: .packages file defines a conflicting entry for package 'expect'.
\ No newline at end of file
diff --git a/pkg/modular_test/test/loader/invalid_packages_error/modules.yaml b/pkg/modular_test/test/loader/invalid_packages_error/modules.yaml
index a5864d3..8daf1249 100644
--- a/pkg/modular_test/test/loader/invalid_packages_error/modules.yaml
+++ b/pkg/modular_test/test/loader/invalid_packages_error/modules.yaml
@@ -1,3 +1,5 @@
 dependencies:
   main:
     - expect
+packages:
+  expect: .
diff --git a/pkg/modular_test/test/loader/valid_packages/.packages b/pkg/modular_test/test/loader/valid_packages/.packages
deleted file mode 100644
index d8e718b..0000000
--- a/pkg/modular_test/test/loader/valid_packages/.packages
+++ /dev/null
@@ -1 +0,0 @@
-js:../../../../js/lib
diff --git a/pkg/modular_test/test/loader/valid_packages/modules.yaml b/pkg/modular_test/test/loader/valid_packages/modules.yaml
index 2ad9483..9a777a1 100644
--- a/pkg/modular_test/test/loader/valid_packages/modules.yaml
+++ b/pkg/modular_test/test/loader/valid_packages/modules.yaml
@@ -2,3 +2,5 @@
   main:
     - js
     - expect
+packages:
+  js: ../../../../js/lib
diff --git a/tests/modular/js_interop/.packages b/tests/modular/js_interop/.packages
deleted file mode 100644
index fce126f..0000000
--- a/tests/modular/js_interop/.packages
+++ /dev/null
@@ -1 +0,0 @@
-js:../../../pkg/js/lib
diff --git a/tests/modular/js_interop/modules.yaml b/tests/modular/js_interop/modules.yaml
index 021eddc..79290b9 100644
--- a/tests/modular/js_interop/modules.yaml
+++ b/tests/modular/js_interop/modules.yaml
@@ -6,3 +6,5 @@
 dependencies:
   main: log
   log: js
+packages:
+  js: ../../../pkg/js/lib
diff --git a/tests/modular/package_imports/.packages b/tests/modular/package_imports/.packages
deleted file mode 100644
index 6c6f5a4..0000000
--- a/tests/modular/package_imports/.packages
+++ /dev/null
@@ -1,6 +0,0 @@
-# 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.
-f0:.
-f1:f1
-a:a
diff --git a/tests/modular/package_imports/modules.yaml b/tests/modular/package_imports/modules.yaml
index a8731e9..e91a134 100644
--- a/tests/modular/package_imports/modules.yaml
+++ b/tests/modular/package_imports/modules.yaml
@@ -11,3 +11,8 @@
   f3: a
   a: f1
   f1: f0
+
+packages:
+  f0: .
+  f1: f1
+  a: a
diff --git a/tests/modular/static_interop_erasure/.packages b/tests/modular/static_interop_erasure/.packages
deleted file mode 100644
index fce126f..0000000
--- a/tests/modular/static_interop_erasure/.packages
+++ /dev/null
@@ -1 +0,0 @@
-js:../../../pkg/js/lib
diff --git a/tests/modular/static_interop_erasure/modules.yaml b/tests/modular/static_interop_erasure/modules.yaml
index 6d1a988..e356ba9 100644
--- a/tests/modular/static_interop_erasure/modules.yaml
+++ b/tests/modular/static_interop_erasure/modules.yaml
@@ -7,3 +7,5 @@
 dependencies:
   main: static_interop
   static_interop: js
+packages:
+  js: ../../../pkg/js/lib
diff --git a/tools/VERSION b/tools/VERSION
index 31ccebe..9df6a11 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 18
 PATCH 0
-PRERELEASE 3
+PRERELEASE 4
 PRERELEASE_PATCH 0
\ No newline at end of file