Version 2.17.0-156.0.dev

Merge commit 'ed68a37df757c3dff4801e52ce8b48ae60688471' into 'dev'
diff --git a/DEPS b/DEPS
index 7e3aaea..276e2bd 100644
--- a/DEPS
+++ b/DEPS
@@ -170,7 +170,7 @@
   "watcher_rev": "f76997ab0c857dc5537ac0975a9ada92b54ef949",
   "webdriver_rev": "ff5ccb1522edf4bed578ead4d65e0cbc1f2c4f02",
   "web_components_rev": "8f57dac273412a7172c8ade6f361b407e2e4ed02",
-  "web_socket_channel_rev": "6448ce532445a8a458fa191d9346df071ae0acad",
+  "web_socket_channel_rev": "99dbdc5769e19b9eeaf69449a59079153c6a8b1f",
   "WebCore_rev": "bcb10901266c884e7b3740abc597ab95373ab55c",
   "webdev_rev": "832b096c0c24798d3df46faa7b7661fe930573c2",
   "webkit_inspection_protocol_rev": "dd6fb5d8b536e19cedb384d0bbf1f5631923f1e8",
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/remove_leading_underscore.dart b/pkg/analysis_server/lib/src/services/correction/dart/remove_leading_underscore.dart
index 75e7dd0a..838e6a2 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/remove_leading_underscore.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/remove_leading_underscore.dart
@@ -48,9 +48,6 @@
       }
     } else if (element is ParameterElement) {
       if (!element.isNamed) {
-        print(node.parent.runtimeType);
-        print(node.parent?.parent.runtimeType);
-        print(node.parent?.parent?.parent.runtimeType);
         var root = node
             .thisOrAncestorMatching((node) =>
                 node.parent is FunctionDeclaration ||
diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
index 85ac443..9b4af58 100644
--- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
+++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
@@ -125,46 +125,81 @@
   status: needsEvaluation
   since: in 2.15
 CompileTimeErrorCode.CONCRETE_CLASS_HAS_ENUM_SUPERINTERFACE:
-  status: needsEvaluation
+  status: noFix
   since: 2.17
+  notes: |-
+    We could potentially offer a fix that would remove all of the types in the
+    class header that are or implement `Enum`, but I think it would be more
+    likely that the user needed to restructure the super interfaces so that
+    `Enum` wasn't included but the rest of the interfaces could be.
 CompileTimeErrorCode.CONCRETE_CLASS_WITH_ABSTRACT_MEMBER:
   status: hasFix
 CompileTimeErrorCode.CONFLICTING_CONSTRUCTOR_AND_STATIC_FIELD:
-  status: needsEvaluation
+  status: noFix
+  notes: |-
+    The fix is to rename one of the two, but we can't know what name to use.
 CompileTimeErrorCode.CONFLICTING_CONSTRUCTOR_AND_STATIC_GETTER:
-  status: needsEvaluation
+  status: noFix
   since: 2.15
+  notes: |-
+    The fix is to rename one of the two, but we can't know what name to use.
 CompileTimeErrorCode.CONFLICTING_CONSTRUCTOR_AND_STATIC_METHOD:
-  status: needsEvaluation
+  status: noFix
+  notes: |-
+    The fix is to rename one of the two, but we can't know what name to use.
 CompileTimeErrorCode.CONFLICTING_CONSTRUCTOR_AND_STATIC_SETTER:
-  status: needsEvaluation
+  status: noFix
   since: 2.15
+  notes: |-
+    The fix is to rename one of the two, but we can't know what name to use.
 CompileTimeErrorCode.CONFLICTING_FIELD_AND_METHOD:
-  status: needsEvaluation
+  status: noFix
+  notes: |-
+    The fix is to rename one of the two, but we can't know what name to use.
 CompileTimeErrorCode.CONFLICTING_GENERIC_INTERFACES:
   status: needsEvaluation
 CompileTimeErrorCode.CONFLICTING_METHOD_AND_FIELD:
-  status: needsEvaluation
+  status: noFix
+  notes: |-
+    The fix is to rename one of the two, but we can't know what name to use.
 CompileTimeErrorCode.CONFLICTING_STATIC_AND_INSTANCE:
-  status: needsEvaluation
+  status: noFix
+  notes: |-
+    The fix is to rename one of the two, but we can't know what name to use.
 CompileTimeErrorCode.CONFLICTING_TYPE_VARIABLE_AND_CLASS:
-  status: needsEvaluation
+  status: noFix
+  notes: |-
+    The fix is to rename one of the two, but we can't know what name to use.
 CompileTimeErrorCode.CONFLICTING_TYPE_VARIABLE_AND_ENUM:
-  status: needsEvaluation
+  status: noFix
   since: 2.17
+  notes: |-
+    The fix is to rename one of the two, but we can't know what name to use.
 CompileTimeErrorCode.CONFLICTING_TYPE_VARIABLE_AND_EXTENSION:
-  status: needsEvaluation
+  status: noFix
+  notes: |-
+    The fix is to rename one of the two, but we can't know what name to use.
 CompileTimeErrorCode.CONFLICTING_TYPE_VARIABLE_AND_MEMBER_CLASS:
-  status: needsEvaluation
+  status: noFix
+  notes: |-
+    The fix is to rename one of the two, but we can't know what name to use.
 CompileTimeErrorCode.CONFLICTING_TYPE_VARIABLE_AND_MEMBER_ENUM:
-  status: needsEvaluation
+  status: noFix
   since: 2.17
+  notes: |-
+    The fix is to rename one of the two, but we can't know what name to use.
 CompileTimeErrorCode.CONFLICTING_TYPE_VARIABLE_AND_MEMBER_EXTENSION:
-  status: needsEvaluation
+  status: noFix
+  notes: |-
+    The fix is to rename one of the two, but we can't know what name to use.
 CompileTimeErrorCode.CONFLICTING_TYPE_VARIABLE_AND_MEMBER_MIXIN:
-  status: needsEvaluation
+  status: noFix
+  notes: |-
+    The fix is to rename one of the two, but we can't know what name to use.
 CompileTimeErrorCode.CONFLICTING_TYPE_VARIABLE_AND_MIXIN:
-  status: needsEvaluation
+  status: noFix
+  notes: |-
+    The fix is to rename one of the two, but we can't know what name to use.
 CompileTimeErrorCode.CONST_CONSTRUCTOR_FIELD_TYPE_MISMATCH:
   status: needsEvaluation
 CompileTimeErrorCode.CONST_CONSTRUCTOR_PARAM_TYPE_MISMATCH:
@@ -267,17 +302,22 @@
 CompileTimeErrorCode.ENUM_CONSTANT_SAME_NAME_AS_ENCLOSING:
   status: needsEvaluation
 CompileTimeErrorCode.ENUM_CONSTANT_WITH_NON_CONST_CONSTRUCTOR: 
-  status: needsEvaluation
+  status: noFix
   since: 2.17
 CompileTimeErrorCode.ENUM_INSTANTIATED_TO_BOUNDS_IS_NOT_WELL_BOUNDED:
-  status: needsEvaluation
+  status: noFix
   since: 2.17
 CompileTimeErrorCode.ENUM_MIXIN_WITH_INSTANCE_VARIABLE:
-  status: needsEvaluation
+  status: noFix
   since: 2.17
+  notes: |-
+    We could potentially offer a fix that would remove the mixin, but I think it
+    would be more likely that the user needed to restructure the mixin so that
+    part of it could be included here.
 CompileTimeErrorCode.ENUM_WITH_ABSTRACT_MEMBER:
-  status: needsEvaluation
+  status: needsFix
   since: 2.17
+  issue: https://github.com/dart-lang/sdk/issues/48478
 CompileTimeErrorCode.EQUAL_ELEMENTS_IN_CONST_SET:
   status: needsEvaluation
 CompileTimeErrorCode.EQUAL_KEYS_IN_CONST_MAP:
@@ -378,17 +418,28 @@
 CompileTimeErrorCode.ILLEGAL_ASYNC_RETURN_TYPE:
   status: hasFix
 CompileTimeErrorCode.ILLEGAL_ENUM_VALUES_DECLARATION:
-  status: needsEvaluation
+  status: noFix
   since: 2.17
+  notes: |-
+    While we could have a fix to remove the declaration, it seems more likely
+    that the user would want to rename it.
 CompileTimeErrorCode.ILLEGAL_ENUM_VALUES_INHERITANCE:
-  status: needsEvaluation
+  status: noFix
   since: 2.17
 CompileTimeErrorCode.ILLEGAL_LANGUAGE_VERSION_OVERRIDE:
-  status: needsEvaluation
+  status: noFix
   since: 2.17
+  notes: |-
+    We could potentially offer to remove the override, or to update it to the
+    lowest legal value, but it isn't clear that either of these is the right
+    fix without knowing why the language override was added.
 CompileTimeErrorCode.ILLEGAL_NON_ABSTRACT_ENUM_INDEX:
-  status: needsEvaluation
+  status: noFix
   since: 2.17
+  notes: |-
+    We could potentially offer to remove the member, but the user probably needs
+    to think about what they were trying to do and it seems more likely that the
+    right fix is to rename the member.
 CompileTimeErrorCode.ILLEGAL_SYNC_GENERATOR_RETURN_TYPE:
   status: hasFix
 CompileTimeErrorCode.IMPLEMENTS_DEFERRED_CLASS:
@@ -484,7 +535,7 @@
 CompileTimeErrorCode.INVALID_OVERRIDE:
   status: hasFix
 CompileTimeErrorCode.INVALID_REFERENCE_TO_GENERATIVE_ENUM_CONSTRUCTOR:
-  status: needsEvaluation
+  status: noFix
   since: 2.17
 CompileTimeErrorCode.INVALID_REFERENCE_TO_THIS:
   status: needsEvaluation
@@ -617,8 +668,9 @@
 CompileTimeErrorCode.NON_BOOL_OPERAND:
   status: needsEvaluation
 CompileTimeErrorCode.NON_CONST_GENERATIVE_ENUM_CONSTRUCTOR:
-  status: needsEvaluation
+  status: needsFix
   since: 2.17
+  issue: https://github.com/dart-lang/sdk/issues/48479
 CompileTimeErrorCode.NON_CONST_MAP_AS_EXPRESSION_STATEMENT:
   status: needsEvaluation
 CompileTimeErrorCode.NON_CONSTANT_ANNOTATION_CONSTRUCTOR:
@@ -786,14 +838,19 @@
   status: needsFix
   since: 2.17
 CompileTimeErrorCode.SUPER_FORMAL_PARAMETER_WITHOUT_ASSOCIATED_NAMED:
-  status: needsEvaluation
+  status: needsFix
   since: 2.17
+  issue: https://github.com/dart-lang/sdk/issues/48480
 CompileTimeErrorCode.SUPER_FORMAL_PARAMETER_WITHOUT_ASSOCIATED_POSITIONAL:
   status: needsFix
   issue: https://github.com/dart-lang/sdk/issues/48359
 CompileTimeErrorCode.SUPER_IN_ENUM_CONSTRUCTOR:
-  status: needsEvaluation
+  status: noFix
   since: 2.17
+  notes: |-
+    We could potentially offer a fix to remove the super invocation, but the
+    user really needs to think about what constructor they were trying to invoke
+    and why in order to really fix the issue.
 CompileTimeErrorCode.SUPER_IN_EXTENSION:
   status: needsEvaluation
 CompileTimeErrorCode.SUPER_IN_INVALID_CONTEXT:
@@ -858,11 +915,13 @@
 CompileTimeErrorCode.UNDEFINED_ENUM_CONSTANT:
   status: needsEvaluation
 CompileTimeErrorCode.UNDEFINED_ENUM_CONSTRUCTOR_NAMED:
-  status: needsEvaluation
+  status: needsFix
   since: 2.17
+  issue: https://github.com/dart-lang/sdk/issues/48481
 CompileTimeErrorCode.UNDEFINED_ENUM_CONSTRUCTOR_UNNAMED:
-  status: needsEvaluation
+  status: needsFix
   since: 2.17
+  issue: https://github.com/dart-lang/sdk/issues/48481
 CompileTimeErrorCode.UNDEFINED_EXTENSION_GETTER:
   status: hasFix
 CompileTimeErrorCode.UNDEFINED_EXTENSION_METHOD:
@@ -922,8 +981,12 @@
 CompileTimeErrorCode.USE_OF_VOID_RESULT:
   status: needsEvaluation
 CompileTimeErrorCode.VALUES_DECLARATION_IN_ENUM:
-  status: needsEvaluation
+  status: noFix
   since: 2.17
+  notes: |-
+    We could potentially offer to remove the member, but the user probably needs
+    to think about what they were trying to do and it seems more likely that the
+    right fix is to rename the member.
 CompileTimeErrorCode.VARIABLE_TYPE_MISMATCH:
   status: needsEvaluation
 CompileTimeErrorCode.WRONG_EXPLICIT_TYPE_PARAMETER_VARIANCE_IN_SUPERINTERFACE:
@@ -942,8 +1005,12 @@
 CompileTimeErrorCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS_CONSTRUCTOR:
   status: hasFix
 CompileTimeErrorCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS_ENUM:
-  status: needsEvaluation
+  status: noFix
   since: 2.17
+  notes: |-
+    We can't know which type arguments to add or remove. (We do have a fix to
+    remove all type arguments that would work here, it just isn't a very good
+    fix to suggest.)
 CompileTimeErrorCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS_EXTENSION:
   status: hasFix
 CompileTimeErrorCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS_FUNCTION:
@@ -1855,10 +1922,17 @@
 ParserErrorCode.DUPLICATED_MODIFIER:
   status: needsEvaluation
 ParserErrorCode.EMPTY_ENUM_BODY:
-  status: needsEvaluation
+  status: noFix
+  notes: |-
+    We can't guess at the names or number of the enum constants that should be
+    added.
 ParserErrorCode.ENUM_CONSTANT_WITH_TYPE_ARGUMENTS_WITHOUT_ARGUMENTS:
-  status: needsEvaluation
+  status: noFix
   since: 2.17
+  notes: |-
+    We could potentially add `()`, but we generally don't have fixes for parse
+    errors because we assume the user is still typing and will soon fix the
+    problem.
 ParserErrorCode.ENUM_IN_CLASS:
   status: needsEvaluation
 ParserErrorCode.EQUALITY_CANNOT_BE_EQUALITY_OPERAND:
diff --git a/pkg/analyzer/lib/src/dart/constant/value.dart b/pkg/analyzer/lib/src/dart/constant/value.dart
index ef76c24..06b9e06 100644
--- a/pkg/analyzer/lib/src/dart/constant/value.dart
+++ b/pkg/analyzer/lib/src/dart/constant/value.dart
@@ -167,16 +167,14 @@
     TypeSystemImpl typeSystem,
     DartType type,
   ) {
-    if (type.element!.library!.isDartCore) {
-      if (type.isDartCoreBool) {
-        return DartObjectImpl(typeSystem, type, BoolState.UNKNOWN_VALUE);
-      } else if (type.isDartCoreDouble) {
-        return DartObjectImpl(typeSystem, type, DoubleState.UNKNOWN_VALUE);
-      } else if (type.isDartCoreInt) {
-        return DartObjectImpl(typeSystem, type, IntState.UNKNOWN_VALUE);
-      } else if (type.isDartCoreString) {
-        return DartObjectImpl(typeSystem, type, StringState.UNKNOWN_VALUE);
-      }
+    if (type.isDartCoreBool) {
+      return DartObjectImpl(typeSystem, type, BoolState.UNKNOWN_VALUE);
+    } else if (type.isDartCoreDouble) {
+      return DartObjectImpl(typeSystem, type, DoubleState.UNKNOWN_VALUE);
+    } else if (type.isDartCoreInt) {
+      return DartObjectImpl(typeSystem, type, IntState.UNKNOWN_VALUE);
+    } else if (type.isDartCoreString) {
+      return DartObjectImpl(typeSystem, type, StringState.UNKNOWN_VALUE);
     }
     return DartObjectImpl(typeSystem, type, GenericState.UNKNOWN_VALUE);
   }
diff --git a/pkg/analyzer/test/generated/constant_test.dart b/pkg/analyzer/test/generated/constant_test.dart
index 9be808b..7e7f0ab 100644
--- a/pkg/analyzer/test/generated/constant_test.dart
+++ b/pkg/analyzer/test/generated/constant_test.dart
@@ -7,6 +7,7 @@
 
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/src/dart/element/element.dart';
+import 'package:analyzer/src/error/codes.dart';
 import 'package:analyzer/src/generated/constant.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -49,6 +50,25 @@
     await _assertValueInt(74 ^ 42, "74 ^ 42");
   }
 
+  test_conditionalExpression_unknownCondition_dynamic() async {
+    await assertErrorsInCode('''
+const bool kIsWeb = identical(0, 0.0);
+const x = kIsWeb ? a : b;
+''', [
+      error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 58,
+          1),
+      error(CompileTimeErrorCode.UNDEFINED_IDENTIFIER, 58, 1),
+      error(CompileTimeErrorCode.UNDEFINED_IDENTIFIER, 62, 1),
+      error(CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE, 62,
+          1),
+    ]);
+
+    var x_result = findElement.topVar('x').evaluationResult;
+    assertDartObjectText(x_result.value, r'''
+dynamic <unknown>
+''');
+  }
+
   test_constructorInvocation_fieldInitializer() async {
     var result = await _getExpressionValue("const C(2)", context: '''
 class C {
diff --git a/pkg/analyzer/test/src/dart/resolution/dart_object_printer.dart b/pkg/analyzer/test/src/dart/resolution/dart_object_printer.dart
index 5514beb..8124576 100644
--- a/pkg/analyzer/test/src/dart/resolution/dart_object_printer.dart
+++ b/pkg/analyzer/test/src/dart/resolution/dart_object_printer.dart
@@ -16,7 +16,14 @@
   void write(DartObjectImpl? object, String indent) {
     if (object != null) {
       var type = object.type;
-      if (type.isDartCoreDouble) {
+      if (object.isUnknown) {
+        sink.write(
+          type.getDisplayString(
+            withNullability: true,
+          ),
+        );
+        sink.writeln(' <unknown>');
+      } else if (type.isDartCoreDouble) {
         sink.write('double ');
         sink.writeln(object.toDoubleValue());
       } else if (type.isDartCoreInt) {
diff --git a/pkg/dartdev/lib/src/analysis_server.dart b/pkg/dartdev/lib/src/analysis_server.dart
index c02c75c..c9fe68b0 100644
--- a/pkg/dartdev/lib/src/analysis_server.dart
+++ b/pkg/dartdev/lib/src/analysis_server.dart
@@ -84,7 +84,7 @@
 
   Future<void> start({bool setAnalysisRoots = true}) async {
     preAnalysisServerStart?.call(commandName, analysisRoots, argResults);
-    final List<String> command = <String>[
+    final command = [
       sdk.analysisServerSnapshot,
       '--${Driver.SUPPRESS_ANALYTICS_FLAG}',
       '--${Driver.CLIENT_ID}=dart-$commandName',
@@ -121,14 +121,15 @@
     // protocol throws an error (INVALID_FILE_PATH_FORMAT) if there is a
     // trailing slash.
     //
-    // The call to absolute.resolveSymbolicLinksSync() canonicalizes the path to
-    // be passed to the analysis server.
-    List<String> analysisRootPaths = analysisRoots.map((root) {
-      return trimEnd(
-          root.absolute.resolveSymbolicLinksSync(), path.context.separator)!;
-    }).toList();
+    // The call to `absolute.resolveSymbolicLinksSync()` canonicalizes the path
+    // to be passed to the analysis server.
+    final analysisRootPaths = [
+      for (final root in analysisRoots)
+        trimEnd(
+            root.absolute.resolveSymbolicLinksSync(), path.context.separator),
+    ];
 
-    onAnalyzing.listen((bool isAnalyzing) {
+    onAnalyzing.listen((isAnalyzing) {
       if (isAnalyzing && _analysisFinished.isCompleted) {
         // Start a new completer, to be completed when we receive the
         // corresponding analysis complete event.
@@ -139,9 +140,9 @@
     });
 
     if (setAnalysisRoots) {
-      await _sendCommand('analysis.setAnalysisRoots', params: <String, dynamic>{
+      await _sendCommand('analysis.setAnalysisRoots', params: {
         'included': analysisRootPaths,
-        'excluded': <String>[]
+        'excluded': [],
       });
     }
   }
diff --git a/pkg/dartdev/lib/src/utils.dart b/pkg/dartdev/lib/src/utils.dart
index f4c5676..a80f7bd 100644
--- a/pkg/dartdev/lib/src/utils.dart
+++ b/pkg/dartdev/lib/src/utils.dart
@@ -31,8 +31,8 @@
 }
 
 /// String utility to trim some suffix from the end of a [String].
-String? trimEnd(String? s, String? suffix) {
-  if (s != null && suffix != null && suffix.isNotEmpty && s.endsWith(suffix)) {
+String trimEnd(String s, String? suffix) {
+  if (suffix != null && suffix.isNotEmpty && s.endsWith(suffix)) {
     return s.substring(0, s.length - suffix.length);
   }
   return s;
diff --git a/pkg/dartdev/test/commands/analyze_test.dart b/pkg/dartdev/test/commands/analyze_test.dart
index 04fc049..a75b441 100644
--- a/pkg/dartdev/test/commands/analyze_test.dart
+++ b/pkg/dartdev/test/commands/analyze_test.dart
@@ -172,15 +172,13 @@
 }
 
 void defineAnalyze() {
-  TestProject? p;
+  late TestProject p;
 
-  setUp(() => p = null);
-
-  tearDown(() async => await p?.dispose());
+  tearDown(() async => await p.dispose());
 
   test('--help', () async {
     p = project();
-    var result = await p!.run(['analyze', '--help']);
+    var result = await p.run(['analyze', '--help']);
 
     expect(result.exitCode, 0);
     expect(result.stderr, isEmpty);
@@ -190,7 +188,7 @@
 
   test('--help --verbose', () async {
     p = project();
-    var result = await p!.run(['analyze', '--help', '--verbose']);
+    var result = await p.run(['analyze', '--help', '--verbose']);
 
     expect(result.exitCode, 0);
     expect(result.stderr, isEmpty);
@@ -206,8 +204,7 @@
     test('folder and file', () async {
       p = project(mainSrc: "int get foo => 'str';\n");
       secondProject = project(mainSrc: "int get foo => 'str';\n");
-      var result =
-          await p!.run(['analyze', p!.dirPath, secondProject.mainPath]);
+      var result = await p.run(['analyze', p.dirPath, secondProject.mainPath]);
 
       expect(result.exitCode, 3);
       expect(result.stderr, isEmpty);
@@ -220,7 +217,7 @@
     test('two folders', () async {
       p = project(mainSrc: "int get foo => 'str';\n");
       secondProject = project(mainSrc: "int get foo => 'str';\n");
-      var result = await p!.run(['analyze', p!.dirPath, secondProject.dirPath]);
+      var result = await p.run(['analyze', p.dirPath, secondProject.dirPath]);
 
       expect(result.exitCode, 3);
       expect(result.stderr, isEmpty);
@@ -233,7 +230,7 @@
 
   test('no such directory', () async {
     p = project();
-    var result = await p!.run(['analyze', '/no/such/dir1/']);
+    var result = await p.run(['analyze', '/no/such/dir1/']);
 
     expect(result.exitCode, 64);
     expect(result.stdout, isEmpty);
@@ -245,7 +242,7 @@
   test('current working directory', () async {
     p = project(mainSrc: 'int get foo => 1;\n');
 
-    var result = await p!.run(['analyze'], workingDir: p!.dirPath);
+    var result = await p.run(['analyze'], workingDir: p.dirPath);
 
     expect(result.exitCode, 0);
     expect(result.stderr, isEmpty);
@@ -255,7 +252,7 @@
   group('single directory', () {
     test('no errors', () async {
       p = project(mainSrc: 'int get foo => 1;\n');
-      var result = await p!.run(['analyze', p!.dirPath]);
+      var result = await p.run(['analyze', p.dirPath]);
 
       expect(result.exitCode, 0);
       expect(result.stderr, isEmpty);
@@ -264,7 +261,7 @@
 
     test('one error', () async {
       p = project(mainSrc: "int get foo => 'str';\n");
-      var result = await p!.run(['analyze', p!.dirPath]);
+      var result = await p.run(['analyze', p.dirPath]);
 
       expect(result.exitCode, 3);
       expect(result.stderr, isEmpty);
@@ -276,7 +273,7 @@
 
     test('two errors', () async {
       p = project(mainSrc: "int get foo => 'str';\nint get bar => 'str';\n");
-      var result = await p!.run(['analyze', p!.dirPath]);
+      var result = await p.run(['analyze', p.dirPath]);
 
       expect(result.exitCode, 3);
       expect(result.stderr, isEmpty);
@@ -287,7 +284,7 @@
   group('single file', () {
     test('no errors', () async {
       p = project(mainSrc: 'int get foo => 1;\n');
-      var result = await p!.run(['analyze', p!.mainPath]);
+      var result = await p.run(['analyze', p.mainPath]);
 
       expect(result.exitCode, 0);
       expect(result.stderr, isEmpty);
@@ -296,7 +293,7 @@
 
     test('one error', () async {
       p = project(mainSrc: "int get foo => 'str';\n");
-      var result = await p!.run(['analyze', p!.mainPath]);
+      var result = await p.run(['analyze', p.mainPath]);
 
       expect(result.exitCode, 3);
       expect(result.stderr, isEmpty);
@@ -311,7 +308,7 @@
     p = project(
         mainSrc: _unusedImportCodeSnippet,
         analysisOptions: _unusedImportAnalysisOptions);
-    var result = await p!.run(['analyze', '--fatal-warnings', p!.dirPath]);
+    var result = await p.run(['analyze', '--fatal-warnings', p.dirPath]);
 
     expect(result.exitCode, equals(2));
     expect(result.stderr, isEmpty);
@@ -322,7 +319,7 @@
     p = project(
         mainSrc: _unusedImportCodeSnippet,
         analysisOptions: _unusedImportAnalysisOptions);
-    var result = await p!.run(['analyze', p!.dirPath]);
+    var result = await p.run(['analyze', p.dirPath]);
 
     expect(result.exitCode, equals(2));
     expect(result.stderr, isEmpty);
@@ -333,7 +330,7 @@
     p = project(
         mainSrc: _unusedImportCodeSnippet,
         analysisOptions: _unusedImportAnalysisOptions);
-    var result = await p!.run(['analyze', '--no-fatal-warnings', p!.dirPath]);
+    var result = await p.run(['analyze', '--no-fatal-warnings', p.dirPath]);
 
     expect(result.exitCode, 0);
     expect(result.stderr, isEmpty);
@@ -342,7 +339,7 @@
 
   test('info implicit no --fatal-infos', () async {
     p = project(mainSrc: dartVersionFilePrefix2_9 + 'String foo() {}');
-    var result = await p!.run(['analyze', p!.dirPath]);
+    var result = await p.run(['analyze', p.dirPath]);
 
     expect(result.exitCode, 0);
     expect(result.stderr, isEmpty);
@@ -351,7 +348,7 @@
 
   test('info --fatal-infos', () async {
     p = project(mainSrc: dartVersionFilePrefix2_9 + 'String foo() {}');
-    var result = await p!.run(['analyze', '--fatal-infos', p!.dirPath]);
+    var result = await p.run(['analyze', '--fatal-infos', p.dirPath]);
 
     expect(result.exitCode, 1);
     expect(result.stderr, isEmpty);
@@ -365,7 +362,7 @@
   var one = 1;
   return result;
 }''');
-    var result = await p!.run(['analyze', '--verbose', p!.dirPath]);
+    var result = await p.run(['analyze', '--verbose', p.dirPath]);
 
     expect(result.exitCode, 3);
     expect(result.stderr, isEmpty);
@@ -387,7 +384,7 @@
 void f() {
   my_foo;
 }''');
-      p!.file('my_packages.json', '''
+      p.file('my_packages.json', '''
 {
   "configVersion": 2,
   "packages": [
@@ -400,10 +397,10 @@
   ]
 }
 ''');
-      var result = await p!.run([
+      var result = await p.run([
         'analyze',
-        '--packages=${p!.findFile('my_packages.json')!.path}',
-        p!.dirPath,
+        '--packages=${p.findFile('my_packages.json')!.path}',
+        p.dirPath,
       ]);
 
       expect(result.exitCode, 0);
@@ -413,10 +410,10 @@
 
     test('not existing', () async {
       p = project();
-      var result = await p!.run([
+      var result = await p.run([
         'analyze',
         '--packages=no.such.file',
-        p!.dirPath,
+        p.dirPath,
       ]);
 
       expect(result.exitCode, 64);
@@ -429,10 +426,10 @@
     var cache = project(name: 'cache');
 
     p = project(mainSrc: 'var v = 0;');
-    var result = await p!.run([
+    var result = await p.run([
       'analyze',
       '--cache=${cache.dirPath}',
-      p!.mainPath,
+      p.mainPath,
     ]);
 
     expect(result.exitCode, 0);
diff --git a/pkg/dartdev/test/utils.dart b/pkg/dartdev/test/utils.dart
index 996b98b..e77686f 100644
--- a/pkg/dartdev/test/utils.dart
+++ b/pkg/dartdev/test/utils.dart
@@ -126,7 +126,7 @@
     List<String> arguments, {
     String? workingDir,
   }) async {
-    _process = await Process.start(
+    final process = await Process.start(
         Platform.resolvedExecutable,
         [
           '--no-analytics',
@@ -137,12 +137,12 @@
           if (logAnalytics) '_DARTDEV_LOG_ANALYTICS': 'true',
           'PUB_CACHE': pubCachePath,
         });
-    final proc = _process!;
-    final stdoutContents = proc.stdout.transform(utf8.decoder).join();
-    final stderrContents = proc.stderr.transform(utf8.decoder).join();
-    final code = await proc.exitCode;
+    _process = process;
+    final stdoutContents = process.stdout.transform(utf8.decoder).join();
+    final stderrContents = process.stderr.transform(utf8.decoder).join();
+    final code = await process.exitCode;
     return ProcessResult(
-      proc.pid,
+      process.pid,
       code,
       await stdoutContents,
       await stderrContents,
diff --git a/pkg/dartdev/test/utils_test.dart b/pkg/dartdev/test/utils_test.dart
index 83f2567..811a0a5 100644
--- a/pkg/dartdev/test/utils_test.dart
+++ b/pkg/dartdev/test/utils_test.dart
@@ -37,10 +37,6 @@
   });
 
   group('trimEnd', () {
-    test('null string', () {
-      expect(trimEnd(null, 'suffix'), null);
-    });
-
     test('null suffix', () {
       expect(trimEnd('string', null), 'string');
     });
diff --git a/pkg/nnbd_migration/lib/src/edge_builder.dart b/pkg/nnbd_migration/lib/src/edge_builder.dart
index ed5ca6e..d7cfb1c 100644
--- a/pkg/nnbd_migration/lib/src/edge_builder.dart
+++ b/pkg/nnbd_migration/lib/src/edge_builder.dart
@@ -35,6 +35,7 @@
 import 'package:nnbd_migration/src/utilities/permissive_mode.dart';
 import 'package:nnbd_migration/src/utilities/resolution_utils.dart';
 import 'package:nnbd_migration/src/utilities/scoped_set.dart';
+import 'package:nnbd_migration/src/utilities/where_not_null_transformer.dart';
 import 'package:nnbd_migration/src/utilities/where_or_null_transformer.dart';
 import 'package:nnbd_migration/src/variables.dart';
 
@@ -236,6 +237,10 @@
   /// equivalents.
   final WhereOrNullTransformer _whereOrNullTransformer;
 
+  /// Helper that assists us in transforming calls to `Iterable.where` to
+  /// `Iterable.whereNotNull`.
+  final WhereNotNullTransformer _whereNotNullTransformer;
+
   /// Deferred processing that should be performed once we have finished
   /// evaluating the decorated type of a method invocation.
   final Map<MethodInvocation, DecoratedType Function(DecoratedType?)>
@@ -252,7 +257,9 @@
       {this.instrumentation})
       : _inheritanceManager = InheritanceManager3(),
         _whereOrNullTransformer =
-            WhereOrNullTransformer(typeProvider, _typeSystem);
+            WhereOrNullTransformer(typeProvider, _typeSystem),
+        _whereNotNullTransformer =
+            WhereNotNullTransformer(typeProvider, _typeSystem);
 
   /// The synthetic element we use as a stand-in for `this` when analyzing
   /// extension methods.
@@ -2499,32 +2506,16 @@
           sourceType = _makeNullableDynamicType(compoundOperatorInfo);
         }
       } else {
-        var transformationInfo =
-            _whereOrNullTransformer.tryTransformOrElseArgument(expression);
-        if (transformationInfo != null) {
-          // Don't build any edges for this argument; if necessary we'll transform
-          // it rather than make things nullable.  But do save the nullability of
-          // the return value of the `orElse` method, so that we can later connect
-          // it to the nullability of the value returned from the method
-          // invocation.
-          var extraNullability = sourceType.returnType!.node;
-          _deferredMethodInvocationProcessing[
-              transformationInfo.methodInvocation] = (methodInvocationType) {
-            var newNode = NullabilityNode.forInferredType(
-                NullabilityNodeTarget.text(
-                    'return value from ${transformationInfo.originalName}'));
-            var origin = IteratorMethodReturnOrigin(
-                source, transformationInfo.methodInvocation);
-            _graph.connect(methodInvocationType!.node, newNode, origin);
-            _graph.connect(extraNullability, newNode, origin);
-            return methodInvocationType.withNode(newNode);
-          };
+        if (_tryTransformOrElse(expression, sourceType) ||
+            _tryTransformWhere(
+                expression, edgeOrigin, sourceType, destinationType!)) {
+          // Nothing further to do.
         } else {
           var hard = _shouldUseHardEdge(expression!,
               isConditionallyExecuted: questionAssignNode != null);
           _checkAssignment(edgeOrigin, FixReasonTarget.root,
               source: sourceType,
-              destination: destinationType!,
+              destination: destinationType,
               hard: hard,
               sourceIsFunctionLiteral: expression is FunctionExpression);
         }
@@ -3465,6 +3456,57 @@
     }
   }
 
+  /// If [node] is an `orElse` argument to an iterable method that should be
+  /// transformed, performs the necessary assignment checks on the expression
+  /// type and returns `true` (this indicates to the caller that no further
+  /// assignment checks need to be performed); otherwise returns `false`.
+  bool _tryTransformOrElse(Expression? expression, DecoratedType sourceType) {
+    var transformationInfo =
+        _whereOrNullTransformer.tryTransformOrElseArgument(expression);
+    if (transformationInfo != null) {
+      // Don't build any edges for this argument; if necessary we'll transform
+      // it rather than make things nullable.  But do save the nullability of
+      // the return value of the `orElse` method, so that we can later connect
+      // it to the nullability of the value returned from the method
+      // invocation.
+      var extraNullability = sourceType.returnType!.node;
+      _deferredMethodInvocationProcessing[transformationInfo.methodInvocation] =
+          (methodInvocationType) {
+        var newNode = NullabilityNode.forInferredType(
+            NullabilityNodeTarget.text(
+                'return value from ${transformationInfo.originalName}'));
+        var origin = IteratorMethodReturnOrigin(
+            source, transformationInfo.methodInvocation);
+        _graph.connect(methodInvocationType!.node, newNode, origin);
+        _graph.connect(extraNullability, newNode, origin);
+        return methodInvocationType.withNode(newNode);
+      };
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  /// If [node] is a call to `Iterable.where` that should be transformed,
+  /// performs the necessary assignment checks on the expression type and
+  /// returns `true` (this indicates to the caller that no further assignment
+  /// checks need to be performed); otherwise returns `false`.
+  bool _tryTransformWhere(Expression? expression, EdgeOrigin edgeOrigin,
+      DecoratedType sourceType, DecoratedType destinationType) {
+    var transformationInfo =
+        _whereNotNullTransformer.tryTransformMethodInvocation(expression);
+    if (transformationInfo != null) {
+      _checkAssignment(edgeOrigin, FixReasonTarget.root,
+          source: _whereNotNullTransformer.transformDecoratedInvocationType(
+              sourceType, _graph),
+          destination: destinationType,
+          hard: false);
+      return true;
+    } else {
+      return false;
+    }
+  }
+
   Never _unimplemented(AstNode? node, String message) {
     StringBuffer buffer = StringBuffer();
     buffer.write(message);
diff --git a/pkg/nnbd_migration/lib/src/fix_builder.dart b/pkg/nnbd_migration/lib/src/fix_builder.dart
index 8ebe8d5..ed266eb 100644
--- a/pkg/nnbd_migration/lib/src/fix_builder.dart
+++ b/pkg/nnbd_migration/lib/src/fix_builder.dart
@@ -38,6 +38,7 @@
 import 'package:nnbd_migration/src/utilities/built_value_transformer.dart';
 import 'package:nnbd_migration/src/utilities/permissive_mode.dart';
 import 'package:nnbd_migration/src/utilities/resolution_utils.dart';
+import 'package:nnbd_migration/src/utilities/where_not_null_transformer.dart';
 import 'package:nnbd_migration/src/utilities/where_or_null_transformer.dart';
 import 'package:nnbd_migration/src/variables.dart';
 import 'package:pub_semver/pub_semver.dart';
@@ -125,10 +126,13 @@
   /// equivalents.
   final WhereOrNullTransformer _whereOrNullTransformer;
 
-  /// Indicates whether an import of package:collection's `IterableExtension`
-  /// will need to be added.
+  /// Helper that assists us in transforming calls to `Iterable.where` to
+  /// `Iterable.whereNotNull`.
+  final WhereNotNullTransformer _whereNotNullTransformer;
+
+  /// The set of extensions that need to be imported from package:collection.
   @visibleForTesting
-  bool needsIterableExtension = false;
+  final Set<String> neededCollectionPackageExtensions = {};
 
   /// Map of additional package dependencies that will be required by the
   /// migrated code.  Keys are package names; values indicate the minimum
@@ -179,7 +183,9 @@
       this._neededPackages)
       : typeProvider = _typeSystem.typeProvider,
         _whereOrNullTransformer =
-            WhereOrNullTransformer(_typeSystem.typeProvider, _typeSystem) {
+            WhereOrNullTransformer(_typeSystem.typeProvider, _typeSystem),
+        _whereNotNullTransformer =
+            WhereNotNullTransformer(_typeSystem.typeProvider, _typeSystem) {
     migrationResolutionHooks._fixBuilder = this;
     assert(_typeSystem.isNonNullableByDefault);
     assert((typeProvider as TypeProviderImpl).isNonNullableByDefault);
@@ -603,6 +609,15 @@
     return change.resultType;
   }
 
+  /// Ensures that the migrated file will contain an import of the extension
+  /// called [extensionName] from `package:collection/collection.dart`, and that
+  /// the pubspec will be appropriately updated (if necessary).
+  void _addCollectionPackageExtension(String extensionName) {
+    _fixBuilder!.neededCollectionPackageExtensions.add(extensionName);
+    _fixBuilder!._neededPackages['collection'] =
+        Version.parse('1.15.0-nullsafety.4');
+  }
+
   DartType _addNullCheck(Expression node, DartType type,
       {AtomicEditInfo? info, HintComment? hint}) {
     var change = _createNullCheckChange(node, type, hint: hint);
@@ -730,32 +745,13 @@
     var ancestor = _findNullabilityContextAncestor(node);
     context ??= _contextTypes[ancestor] ?? DynamicTypeImpl.instance;
     if (!_isSubtypeOrCoercible(type, context)) {
-      var transformationInfo =
-          _fixBuilder!._whereOrNullTransformer.tryTransformOrElseArgument(node);
-      if (transformationInfo != null) {
-        // We can fix this by dropping the node and changing the method call.
-        _fixBuilder!.needsIterableExtension = true;
-        _fixBuilder!._neededPackages['collection'] =
-            Version.parse('1.15.0-nullsafety.4');
-        var info = AtomicEditInfo(
-            NullabilityFixDescription.changeMethodName(
-                transformationInfo.originalName,
-                transformationInfo.replacementName),
-            {});
-        (_fixBuilder!._getChange(transformationInfo.methodInvocation.methodName)
-                as NodeChangeForMethodName)
-            .replaceWith(transformationInfo.replacementName, info);
-        (_fixBuilder!._getChange(
-                    transformationInfo.methodInvocation.argumentList)
-                as NodeChangeForArgumentList)
-            .dropArgument(transformationInfo.orElseArgument, info);
-        _deferredMethodInvocationProcessing[
-                transformationInfo.methodInvocation] =
-            (methodInvocationType) => _fixBuilder!._typeSystem
-                .makeNullable(methodInvocationType as TypeImpl);
-        return type;
-      }
-      return _addCastOrNullCheck(node, type, context);
+      return _tryTransformOrElse(node, type) ??
+          _tryTransformWhere(node, type) ??
+          _addCastOrNullCheck(node, type, context);
+    } else {
+      // Even if the type is a subtype of its context, we still want to
+      // transform `.where`.
+      type = _tryTransformWhere(node, type) ?? type;
     }
     if (!_fixBuilder!._typeSystem.isNullable(type)) return type;
     if (_needsNullCheckDueToStructure(ancestor)) {
@@ -847,6 +843,63 @@
         .toList();
   }
 
+  /// If [node] is an `orElse` argument to an iterable method that should be
+  /// transformed, transforms it and returns [type] (this indicates to the
+  /// caller that no further transformations to this expression need to be
+  /// considered); otherwise returns `null`.
+  DartType? _tryTransformOrElse(Expression node, DartType type) {
+    var transformationInfo =
+        _fixBuilder!._whereOrNullTransformer.tryTransformOrElseArgument(node);
+    if (transformationInfo != null) {
+      // We can fix this by dropping the node and changing the method call.
+      _addCollectionPackageExtension('IterableExtension');
+      var info = AtomicEditInfo(
+          NullabilityFixDescription.changeMethodName(
+              transformationInfo.originalName,
+              transformationInfo.replacementName),
+          {});
+      (_fixBuilder!._getChange(transformationInfo.methodInvocation.methodName)
+              as NodeChangeForMethodName)
+          .replaceWith(transformationInfo.replacementName, info);
+      (_fixBuilder!._getChange(transformationInfo.methodInvocation.argumentList)
+              as NodeChangeForArgumentList)
+          .dropArgument(transformationInfo.orElseArgument, info);
+      _deferredMethodInvocationProcessing[transformationInfo.methodInvocation] =
+          (methodInvocationType) => _fixBuilder!._typeSystem
+              .makeNullable(methodInvocationType as TypeImpl);
+      return type;
+    }
+    return null;
+  }
+
+  /// If [node] is a call to `Iterable.where` that should be transformed,
+  /// transforms it and returns the type of the transformed method call (this
+  /// indicates to the caller that no further transformations to this expression
+  /// need to be considered); otherwise returns `null`.
+  DartType? _tryTransformWhere(Expression node, DartType type) {
+    var transformationInfo = _fixBuilder!._whereNotNullTransformer
+        .tryTransformMethodInvocation(node);
+    if (transformationInfo != null) {
+      // We can fix this by dropping the method call's argument and changing the
+      // call.
+      _addCollectionPackageExtension('IterableNullableExtension');
+      var info = AtomicEditInfo(
+          NullabilityFixDescription.changeMethodName(
+              transformationInfo.originalName,
+              transformationInfo.replacementName),
+          {});
+      (_fixBuilder!._getChange(transformationInfo.methodInvocation.methodName)
+              as NodeChangeForMethodName)
+          .replaceWith(transformationInfo.replacementName, info);
+      (_fixBuilder!._getChange(transformationInfo.methodInvocation.argumentList)
+              as NodeChangeForArgumentList)
+          .dropArgument(transformationInfo.argument, info);
+      return _fixBuilder!._whereNotNullTransformer
+          .transformPostMigrationInvocationType(type);
+    }
+    return null;
+  }
+
   /// Runs the computation in [compute].  If an exception occurs and
   /// [_fixBuilder.listener] is non-null, the exception is reported to the
   /// listener and [fallback] is called to produce a result.  Otherwise the
@@ -1012,19 +1065,25 @@
       (_fixBuilder._getChange(node) as NodeChangeForCompilationUnit)
           .removeLanguageVersionComment = true;
     }
-    if (_fixBuilder.needsIterableExtension) {
+    if (_fixBuilder.neededCollectionPackageExtensions.isNotEmpty) {
       var packageCollectionImport =
           _findImportDirective(node, 'package:collection/collection.dart');
       if (packageCollectionImport != null) {
         for (var combinator in packageCollectionImport.combinators) {
           if (combinator is ShowCombinator) {
-            _ensureShows(combinator, 'IterableExtension');
+            for (var extensionName
+                in _fixBuilder.neededCollectionPackageExtensions) {
+              _ensureShows(combinator, extensionName);
+            }
           }
         }
       } else {
-        (_fixBuilder._getChange(node) as NodeChangeForCompilationUnit)
-            .addImport(
-                'package:collection/collection.dart', 'IterableExtension');
+        var change =
+            _fixBuilder._getChange(node) as NodeChangeForCompilationUnit;
+        for (var extensionName
+            in _fixBuilder.neededCollectionPackageExtensions) {
+          change.addImport('package:collection/collection.dart', extensionName);
+        }
       }
     }
     super.visitCompilationUnit(node);
diff --git a/pkg/nnbd_migration/lib/src/utilities/where_not_null_transformer.dart b/pkg/nnbd_migration/lib/src/utilities/where_not_null_transformer.dart
new file mode 100644
index 0000000..cc27031
--- /dev/null
+++ b/pkg/nnbd_migration/lib/src/utilities/where_not_null_transformer.dart
@@ -0,0 +1,152 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
+import 'package:analyzer/dart/element/element.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/element/type.dart';
+import 'package:nnbd_migration/src/decorated_type.dart';
+import 'package:nnbd_migration/src/nullability_node.dart';
+
+/// Information about a method call that we might want to transform into a call
+/// to `whereNotNull` counterpart.  See [WhereNotNullTransformer] for more
+/// information.
+class WhereNotNullTransformationInfo {
+  /// AST node of the method invocation.
+  final MethodInvocation methodInvocation;
+
+  /// AST node of the argument of the method invocation.
+  final Expression argument;
+
+  /// Original name of the method being called, prior to transformation.
+  final String originalName;
+
+  WhereNotNullTransformationInfo(
+      this.methodInvocation, this.argument, this.originalName);
+
+  /// New method to call, after transformation.
+  String get replacementName => 'whereNotNull';
+}
+
+/// Methods to assist in transforming calls to the `Iterable` method `where`
+/// into calls to the `package:collection` method `whereNotNull`, where
+/// possible.
+///
+/// An example of the kind of code that can be transformed is:
+///
+///     Iterable<int> f(List<int/*?*/> x) => x.where((y) => y != null);
+///
+/// We transform this into:
+///
+///     Iterable<int> f(List<int?> x) => x.whereNotNull();
+///
+/// Without this transformation, the migrated result would have been:
+///
+///     Iterable<int?> f(List<int/*?*/> x) => x.where((y) => y != null);
+///
+/// Which would have placed an otherwise unnecessary requirement on callers to
+/// handle `null` values in the resulting iterable.
+class WhereNotNullTransformer {
+  final TypeProvider _typeProvider;
+
+  final TypeSystem _typeSystem;
+
+  WhereNotNullTransformer(this._typeProvider, this._typeSystem);
+
+  /// Transforms the [DecoratedType] of an invocation of `.where` to the
+  /// [DecoratedType] of the corresponding invocation of `.whereNotNull` that
+  /// will replace it.
+  ///
+  /// The transformation is that the type argument to `Iterable` is made
+  /// non-nullable, so that nullability doesn't unnecessarily propagate to other
+  /// parts of the code.
+  DecoratedType transformDecoratedInvocationType(
+      DecoratedType decoratedType, NullabilityGraph graph) {
+    var type = decoratedType.type;
+    var typeArguments = decoratedType.typeArguments;
+    if (type is InterfaceType &&
+        type.element == _typeProvider.iterableElement &&
+        typeArguments.length == 1) {
+      return DecoratedType(type, decoratedType.node,
+          typeArguments: [typeArguments.single?.withNode(graph.never)]);
+    }
+    return decoratedType;
+  }
+
+  /// Transforms the post-migration type of an invocation of `.where` to the
+  /// type of the corresponding invocation of `.whereNotNull` that will replace
+  /// it.
+  ///
+  /// The transformation is that the type argument to `Iterable` is made
+  /// non-nullable, so that we don't try to introduce any unnecessary null
+  /// checks or type casts in other parts of the code.
+  DartType transformPostMigrationInvocationType(DartType type) {
+    if (type is InterfaceType &&
+        type.element == _typeProvider.iterableElement) {
+      var typeArguments = type.typeArguments;
+      if (typeArguments.length == 1) {
+        return InterfaceTypeImpl(
+            element: type.element,
+            typeArguments: [_typeSystem.promoteToNonNull(typeArguments.single)],
+            nullabilitySuffix: type.nullabilitySuffix);
+      }
+    }
+    return type;
+  }
+
+  /// If [node] is a call that can be transformed, returns information about the
+  /// transformable call; otherwise returns `null`.
+  WhereNotNullTransformationInfo? tryTransformMethodInvocation(AstNode? node) {
+    if (node is MethodInvocation) {
+      if (!_isTransformableMethod(node.methodName.staticElement)) return null;
+      var arguments = node.argumentList.arguments;
+      if (arguments.length != 1) return null;
+      var argument = arguments[0];
+      if (!_isClosureCheckingNotNull(argument)) return null;
+      return WhereNotNullTransformationInfo(
+          node, argument, node.methodName.name);
+    }
+    return null;
+  }
+
+  /// Checks whether [expression] is of the form `(x) => x != null`.
+  bool _isClosureCheckingNotNull(Expression expression) {
+    if (expression is! FunctionExpression) return false;
+    if (expression.typeParameters != null) return false;
+    var parameters = expression.parameters!.parameters;
+    if (parameters.length != 1) return false;
+    var parameter = parameters[0];
+    if (parameter.isNamed) return false;
+    var body = expression.body;
+    if (body is! ExpressionFunctionBody) return false;
+    var returnedExpression = body.expression;
+    if (returnedExpression is! BinaryExpression) return false;
+    if (returnedExpression.operator.type != TokenType.BANG_EQ) return false;
+    var lhs = returnedExpression.leftOperand;
+    if (lhs is! SimpleIdentifier) return false;
+    if (lhs.staticElement != parameter.declaredElement) return false;
+    if (returnedExpression.rightOperand is! NullLiteral) return false;
+    return true;
+  }
+
+  /// Determines if [element] is a declaration of `.where` for which calls can
+  /// be transformed.
+  bool _isTransformableMethod(Element? element) {
+    if (element is MethodElement) {
+      if (element.isStatic) return false;
+      if (element.name != 'where') return false;
+      var enclosingElement = element.declaration.enclosingElement;
+      if (enclosingElement is ClassElement) {
+        // If the class is `Iterable` or a subtype of it, we consider the user
+        // to be calling a transformable method.
+        return _typeSystem.isSubtypeOf(
+            enclosingElement.thisType, _typeProvider.iterableDynamicType);
+      }
+    }
+    return false;
+  }
+}
diff --git a/pkg/nnbd_migration/test/api_test.dart b/pkg/nnbd_migration/test/api_test.dart
index c567c61..e2799e8 100644
--- a/pkg/nnbd_migration/test/api_test.dart
+++ b/pkg/nnbd_migration/test/api_test.dart
@@ -9425,6 +9425,58 @@
 ''';
     await _checkSingleFileChanges(content, expected, warnOnWeakCode: true);
   }
+
+  Future<void> test_whereNotNull() async {
+    var content = '''
+Iterable<String> f(Iterable<String/*?*/> it) => it.where((s) => s != null);
+''';
+    var expected = '''
+import 'package:collection/collection.dart' show IterableNullableExtension;
+
+Iterable<String> f(Iterable<String?> it) => it.whereNotNull();
+''';
+    await _checkSingleFileChanges(content, expected);
+  }
+
+  Future<void> test_whereNotNull_and_firstWhereOrNull() async {
+    var content = '''
+Iterable<String> f(Iterable<String/*?*/> it) => it.where((s) => s != null);
+int g(Iterable<int> it) => it.firstWhere((i) => i != 0, orElse: () => null);
+''';
+    var expected = '''
+import 'package:collection/collection.dart' show IterableExtension, IterableNullableExtension;
+
+Iterable<String> f(Iterable<String?> it) => it.whereNotNull();
+int? g(Iterable<int> it) => it.firstWhereOrNull((i) => i != 0);
+''';
+    await _checkSingleFileChanges(content, expected);
+  }
+
+  Future<void> test_whereNotNull_complexType() async {
+    var content = '''
+Iterable<Map<String, int>> f(Iterable<Map<String/*?*/, int>/*?*/> it)
+    => it.where((m) => m != null);
+''';
+    var expected = '''
+import 'package:collection/collection.dart' show IterableNullableExtension;
+
+Iterable<Map<String?, int>> f(Iterable<Map<String?, int>?> it)
+    => it.whereNotNull();
+''';
+    await _checkSingleFileChanges(content, expected);
+  }
+
+  Future<void> test_whereNotNull_noContext() async {
+    var content = '''
+f(Iterable<String/*?*/> it) => it.where((s) => s != null);
+''';
+    var expected = '''
+import 'package:collection/collection.dart' show IterableNullableExtension;
+
+f(Iterable<String?> it) => it.whereNotNull();
+''';
+    await _checkSingleFileChanges(content, expected);
+  }
 }
 
 @reflectiveTest
diff --git a/pkg/nnbd_migration/test/fix_builder_test.dart b/pkg/nnbd_migration/test/fix_builder_test.dart
index 76c2bca..322ab2a 100644
--- a/pkg/nnbd_migration/test/fix_builder_test.dart
+++ b/pkg/nnbd_migration/test/fix_builder_test.dart
@@ -1433,7 +1433,7 @@
       // Behavior of the null literal doesn't matter because it's being dropped.
       findNode.nullLiteral('null'): anything
     });
-    expect(fixBuilder.needsIterableExtension, true);
+    expect(fixBuilder.neededCollectionPackageExtensions, {'IterableExtension'});
   }
 
   Future<void> test_functionExpressionInvocation_dynamic() async {
@@ -3812,13 +3812,29 @@
     });
   }
 
+  Future<void> test_where_transform() async {
+    await analyze('''
+Iterable<int> _f(Iterable<int/*?*/> x) => x.where((e) => e != null);
+''');
+    var methodInvocation = findNode.methodInvocation('where');
+    var functionExpression = findNode.functionExpression('(e) => e != null');
+    var fixBuilder =
+        visitSubexpression(methodInvocation, 'Iterable<int>', changes: {
+      methodInvocation.methodName: isMethodNameChange('whereNotNull'),
+      methodInvocation.argumentList:
+          isDropArgument({functionExpression: anything}),
+    });
+    expect(fixBuilder.neededCollectionPackageExtensions,
+        {'IterableNullableExtension'});
+  }
+
   void visitAll(
       {Map<AstNode, Matcher> changes = const <Expression, Matcher>{},
       Map<AstNode, Set<Problem>> problems = const <AstNode, Set<Problem>>{},
       bool injectNeedsIterableExtension = false}) {
     var fixBuilder = _createFixBuilder(testUnit!);
     if (injectNeedsIterableExtension) {
-      fixBuilder.needsIterableExtension = true;
+      fixBuilder.neededCollectionPackageExtensions.add('IterableExtension');
     }
     fixBuilder.visitAll();
     expect(scopedChanges(fixBuilder, testUnit), changes);
@@ -3930,16 +3946,16 @@
           .having((c) => c.expressionChangeInfos.single,
               'expressionChangeInfos.single', infoMatcher);
 
-  TypeMatcher<T> havingNullCheckWithInfo(dynamic matcher) =>
-      havingExpressionChange(TypeMatcher<NullCheckChange>(), matcher);
-
-  TypeMatcher<T> havingNoValidMigrationWithInfo(dynamic matcher) =>
-      havingExpressionChange(TypeMatcher<NoValidMigrationChange>(), matcher);
-
   TypeMatcher<T> havingIndroduceAsWithInfo(
           dynamic typeStringMatcher, dynamic infoMatcher) =>
       havingExpressionChange(
           TypeMatcher<IntroduceAsChange>().having(
               (c) => c.type.toString(), 'type (string)', typeStringMatcher),
           infoMatcher);
+
+  TypeMatcher<T> havingNoValidMigrationWithInfo(dynamic matcher) =>
+      havingExpressionChange(TypeMatcher<NoValidMigrationChange>(), matcher);
+
+  TypeMatcher<T> havingNullCheckWithInfo(dynamic matcher) =>
+      havingExpressionChange(TypeMatcher<NullCheckChange>(), matcher);
 }
diff --git a/pkg/nnbd_migration/test/utilities/test_all.dart b/pkg/nnbd_migration/test/utilities/test_all.dart
index dd93187..9c04ec1e 100644
--- a/pkg/nnbd_migration/test/utilities/test_all.dart
+++ b/pkg/nnbd_migration/test/utilities/test_all.dart
@@ -9,6 +9,8 @@
 import 'source_edit_diff_formatter_test.dart'
     as source_edit_diff_formatter_test;
 import 'subprocess_launcher_test.dart' as subprocess_launcher_test;
+import 'where_not_null_transformer_test.dart'
+    as where_not_null_transformer_test;
 import 'where_or_null_transformer_test.dart' as where_or_null_transformer_test;
 
 main() {
@@ -17,6 +19,7 @@
     scoped_set_test.main();
     source_edit_diff_formatter_test.main();
     subprocess_launcher_test.main();
+    where_not_null_transformer_test.main();
     where_or_null_transformer_test.main();
   });
 }
diff --git a/pkg/nnbd_migration/test/utilities/where_not_null_transformer_test.dart b/pkg/nnbd_migration/test/utilities/where_not_null_transformer_test.dart
new file mode 100644
index 0000000..a4e9f47
--- /dev/null
+++ b/pkg/nnbd_migration/test/utilities/where_not_null_transformer_test.dart
@@ -0,0 +1,212 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/element/type_provider.dart';
+import 'package:analyzer/dart/element/type_system.dart';
+import 'package:nnbd_migration/src/utilities/where_not_null_transformer.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../abstract_single_unit.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(WhereNotNullTransformerTest);
+  });
+}
+
+@reflectiveTest
+class WhereNotNullTransformerTest extends AbstractSingleUnitTest {
+  late WhereNotNullTransformer transformer;
+
+  TypeProvider get typeProvider => testAnalysisResult.typeProvider;
+
+  TypeSystem get typeSystem => testAnalysisResult.typeSystem;
+
+  Future<void> analyze(String code) async {
+    await resolveTestUnit(code);
+    transformer = WhereNotNullTransformer(typeProvider, typeSystem);
+  }
+
+  Future<void> test_match() async {
+    await analyze('''
+f(List<int> x) => x.where((i) => i != null);
+''');
+    var methodInvocation = findNode.methodInvocation('.where');
+    var transformationInfo =
+        transformer.tryTransformMethodInvocation(methodInvocation)!;
+    expect(transformationInfo, isNotNull);
+    expect(transformationInfo.methodInvocation, same(methodInvocation));
+    expect(transformationInfo.argument,
+        same(findNode.functionExpression('(i) => i != null')));
+    expect(transformationInfo.originalName, 'where');
+    expect(transformationInfo.replacementName, 'whereNotNull');
+  }
+
+  Future<void> test_match_extended() async {
+    await analyze('''
+abstract class C implements Iterable<int> {
+  Iterable<int> where(bool test(int element)) => null;
+}
+f(C c) => c.where((i) => i != null);
+''');
+    var methodInvocation = findNode.methodInvocation('.where');
+    var transformationInfo =
+        transformer.tryTransformMethodInvocation(methodInvocation)!;
+    expect(transformationInfo, isNotNull);
+    expect(transformationInfo.methodInvocation, same(methodInvocation));
+    expect(transformationInfo.argument,
+        same(findNode.functionExpression('(i) => i != null')));
+    expect(transformationInfo.originalName, 'where');
+    expect(transformationInfo.replacementName, 'whereNotNull');
+  }
+
+  Future<void> test_match_returns_subtype() async {
+    await analyze('''
+abstract class C implements Iterable<int> {
+  List<int> where(bool test(int element)) => null;
+}
+f(C c) => c.where((i) => i != null);
+''');
+    var methodInvocation = findNode.methodInvocation('.where');
+    var transformationInfo =
+        transformer.tryTransformMethodInvocation(methodInvocation)!;
+    expect(transformationInfo, isNotNull);
+    expect(transformationInfo.methodInvocation, same(methodInvocation));
+    expect(transformationInfo.argument,
+        same(findNode.functionExpression('(i) => i != null')));
+    expect(transformationInfo.originalName, 'where');
+    expect(transformationInfo.replacementName, 'whereNotNull');
+  }
+
+  Future<void> test_mismatch_closure_block_typed() async {
+    await analyze('''
+abstract class C implements Iterable<int> {
+  Iterable<int> where(bool test(int element)) => null;
+}
+f(C c) => c.where((i) { return i != null; });
+''');
+    expect(
+        transformer
+            .tryTransformMethodInvocation(findNode.methodInvocation('.where')),
+        isNull);
+  }
+
+  Future<void> test_mismatch_closure_lhs_not_identifier() async {
+    await analyze('''
+abstract class C implements Iterable<int> {
+  Iterable<int> where(bool test(int element)) => null;
+}
+f(C c) => c.where((i) => 2*i != null);
+''');
+    expect(
+        transformer
+            .tryTransformMethodInvocation(findNode.methodInvocation('.where')),
+        isNull);
+  }
+
+  Future<void> test_mismatch_closure_lhs_wrong_element() async {
+    await analyze('''
+abstract class C implements Iterable<int> {
+  Iterable<int> where(bool test(int element)) => null;
+}
+f(C c) => c.where((i) => c != null);
+''');
+    expect(
+        transformer
+            .tryTransformMethodInvocation(findNode.methodInvocation('.where')),
+        isNull);
+  }
+
+  Future<void> test_mismatch_closure_non_binary_expression() async {
+    await analyze('''
+abstract class C implements Iterable<int> {
+  Iterable<int> where(bool test(int element)) => null;
+}
+f(C c) => c.where((i) => true);
+''');
+    expect(
+        transformer
+            .tryTransformMethodInvocation(findNode.methodInvocation('.where')),
+        isNull);
+  }
+
+  Future<void> test_mismatch_closure_wrong_operator() async {
+    await analyze('''
+abstract class C implements Iterable<int> {
+  Iterable<int> where(bool test(int element)) => null;
+}
+f(C c) => c.where((i) => i == null);
+''');
+    expect(
+        transformer
+            .tryTransformMethodInvocation(findNode.methodInvocation('.where')),
+        isNull);
+  }
+
+  Future<void> test_mismatch_extension_method() async {
+    await analyze('''
+extension on String {
+  Iterable<int> where(bool test(int element)) => null;
+}
+f(String s) => s.where((i) => i != null);
+''');
+    expect(
+        transformer
+            .tryTransformMethodInvocation(findNode.methodInvocation('.where')),
+        isNull);
+  }
+
+  Future<void> test_mismatch_misnamed_method() async {
+    await analyze('''
+abstract class C implements Iterable<int> {
+  Iterable<int> fooBar(bool test(int element)) => null;
+}
+f(C c) => c.fooBar((i) => i != null);
+''');
+    expect(
+        transformer
+            .tryTransformMethodInvocation(findNode.methodInvocation('.fooBar')),
+        isNull);
+  }
+
+  Future<void> test_mismatch_not_a_subtype_of_iterable() async {
+    await analyze('''
+abstract class C {
+  Iterable<int> where(bool test(int element)) => null;
+}
+f(C c) => c.where((i) => i != null);
+''');
+    expect(
+        transformer
+            .tryTransformMethodInvocation(findNode.methodInvocation('.where')),
+        isNull);
+  }
+
+  Future<void> test_mismatch_rhs_not_null() async {
+    await analyze('''
+abstract class C implements Iterable<int> {
+  Iterable<int> where(bool test(int element)) => null;
+}
+f(C c) => c.where((i) => i != 0);
+''');
+    expect(
+        transformer
+            .tryTransformMethodInvocation(findNode.methodInvocation('.where')),
+        isNull);
+  }
+
+  Future<void> test_mismatch_too_many_arguments() async {
+    await analyze('''
+abstract class C implements Iterable<int> {
+  Iterable<int> where(bool test(int element), {int x}) => null;
+}
+f(C c) => c.where((i) => i != null, x: 0);
+''');
+    expect(
+        transformer
+            .tryTransformMethodInvocation(findNode.methodInvocation('.where')),
+        isNull);
+  }
+}
diff --git a/tools/VERSION b/tools/VERSION
index c429a9a..2401c9d 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 17
 PATCH 0
-PRERELEASE 155
+PRERELEASE 156
 PRERELEASE_PATCH 0
\ No newline at end of file