Version 2.12.0-246.0.dev

Merge commit 'b94adb2bc2d53897a727e3ae1dcdf10c74948659' into 'dev'
diff --git a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
index 612de52..6dbb243 100644
--- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
@@ -998,14 +998,14 @@
     message: r"""Compiling with sound null safety""");
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
-const Code<Null> codeCompilingWithUnsoundNullSafety =
-    messageCompilingWithUnsoundNullSafety;
+const Code<Null> codeCompilingWithoutSoundNullSafety =
+    messageCompilingWithoutSoundNullSafety;
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
-const MessageCode messageCompilingWithUnsoundNullSafety = const MessageCode(
-    "CompilingWithUnsoundNullSafety",
+const MessageCode messageCompilingWithoutSoundNullSafety = const MessageCode(
+    "CompilingWithoutSoundNullSafety",
     severity: Severity.info,
-    message: r"""Compiling with unsound null safety""");
+    message: r"""Compiling without sound null safety""");
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Template<
diff --git a/pkg/analysis_server/doc/api.html b/pkg/analysis_server/doc/api.html
index 0bce9d2..3bf7d05 100644
--- a/pkg/analysis_server/doc/api.html
+++ b/pkg/analysis_server/doc/api.html
@@ -109,7 +109,7 @@
 <body>
 <h1>Analysis Server API Specification</h1>
 <h1 style="color:#999999">Version
-  1.32.1
+  1.32.2
 </h1>
 <p>
   This document contains a specification of the API provided by the
@@ -236,9 +236,13 @@
   ignoring the item or treating it with some default/fallback handling.
 </p>
 <h3>Changelog</h3>
+<h4>1.32.2</h4>
+<ul>
+  <li>Added <tt>FoldingKind.COMMENT</tt> for folding regions for blocks of comments.</li>
+</ul>
 <h4>1.32.1</h4>
 <ul>
-  <li>Added <tt>CompletionSuggestionKind.PACKAGE_NAME</tt> for Pub package name completions in <tt>pubspec.yaml</tt></li>
+  <li>Added <tt>CompletionSuggestionKind.PACKAGE_NAME</tt> for Pub package name completions in <tt>pubspec.yaml</tt>.</li>
 </ul>
 <h3>Domains</h3>
 <p>
@@ -4226,7 +4230,7 @@
       An enumeration of the kinds of folding regions.
     </p>
     
-  <dl><dt class="value">ANNOTATIONS</dt><dt class="value">BLOCK</dt><dt class="value">CLASS_BODY</dt><dt class="value">DIRECTIVES</dt><dt class="value">DOCUMENTATION_COMMENT</dt><dt class="value">FILE_HEADER</dt><dt class="value">FUNCTION_BODY</dt><dt class="value">INVOCATION</dt><dt class="value">LITERAL</dt></dl></dd><dt class="typeDefinition"><a name="type_FoldingRegion">FoldingRegion: object</a></dt><dd>
+  <dl><dt class="value">ANNOTATIONS</dt><dt class="value">BLOCK</dt><dt class="value">CLASS_BODY</dt><dt class="value">COMMENT</dt><dt class="value">DIRECTIVES</dt><dt class="value">DOCUMENTATION_COMMENT</dt><dt class="value">FILE_HEADER</dt><dt class="value">FUNCTION_BODY</dt><dt class="value">INVOCATION</dt><dt class="value">LITERAL</dt></dl></dd><dt class="typeDefinition"><a name="type_FoldingRegion">FoldingRegion: object</a></dt><dd>
     <p>
       A description of a region that can be folded.
     </p>
diff --git a/pkg/analysis_server/lib/protocol/protocol_constants.dart b/pkg/analysis_server/lib/protocol/protocol_constants.dart
index 4ad5e63..68ea10f 100644
--- a/pkg/analysis_server/lib/protocol/protocol_constants.dart
+++ b/pkg/analysis_server/lib/protocol/protocol_constants.dart
@@ -6,7 +6,7 @@
 // To regenerate the file, use the script
 // "pkg/analysis_server/tool/spec/generate_files".
 
-const String PROTOCOL_VERSION = '1.32.1';
+const String PROTOCOL_VERSION = '1.32.2';
 
 const String ANALYSIS_NOTIFICATION_ANALYZED_FILES = 'analysis.analyzedFiles';
 const String ANALYSIS_NOTIFICATION_ANALYZED_FILES_DIRECTORIES = 'directories';
diff --git a/pkg/analysis_server/lib/src/computer/computer_folding.dart b/pkg/analysis_server/lib/src/computer/computer_folding.dart
index 725ea9a..2a8bc96 100644
--- a/pkg/analysis_server/lib/src/computer/computer_folding.dart
+++ b/pkg/analysis_server/lib/src/computer/computer_folding.dart
@@ -49,7 +49,6 @@
 
   /// Returns a list of folding regions, not `null`.
   List<FoldingRegion> compute() {
-    _addFileHeaderRegion();
     _unit.accept(_DartUnitFoldingComputerVisitor(this));
 
     if (_firstDirective != null &&
@@ -61,39 +60,90 @@
           _lastDirective.end - _firstDirective.keyword.end));
     }
 
+    _addCommentRegions();
+
     return _foldingRegions;
   }
 
-  void _addFileHeaderRegion() {
-    var firstToken = _unit.beginToken;
-    while (firstToken?.type == TokenType.SCRIPT_TAG) {
-      firstToken = firstToken.next;
+  /// Create a folding region for the provided comment, reading forwards if neccesary.
+  ///
+  /// If [mayBeFileHeader] is true, the token will be considered a file header
+  /// if comment is a single-line-comment and there is a blank line or another
+  /// comment type after it.
+  ///
+  /// Returns the next comment to be processed or null if there are no more comments
+  /// to process in the chain.
+  Token _addCommentRegion(Token commentToken, {bool mayBeFileHeader = false}) {
+    int offset, end;
+    var isFileHeader = false;
+    Token nextComment;
+
+    if (commentToken.type == TokenType.MULTI_LINE_COMMENT) {
+      // Multiline comments already span all of their lines but the folding
+      // region should start at the end of the first line.
+      offset = commentToken.offset + (commentToken.eolOffset ?? 0);
+      end = commentToken.end;
+      nextComment = commentToken.next;
+    } else {
+      // Single line comments need grouping together explicitly but should
+      // only group if the prefix is the same and up to any blank line.
+      final isTripleSlash = commentToken.isTripleSlash;
+      // Track the last comment that belongs to this folding region.
+      var lastComment = commentToken;
+      var current = lastComment.next;
+      while (current != null &&
+          current.type == lastComment.type &&
+          current.isTripleSlash == isTripleSlash &&
+          !_hasBlankLineBetween(lastComment.end, current.offset)) {
+        lastComment = current;
+        current = current.next;
+      }
+
+      // For single line comments we prefer to start the range at the end of
+      // first token so the first line is still visible when the range is
+      // collapsed.
+      offset = commentToken.end;
+      end = lastComment.end;
+      nextComment = lastComment.next;
+
+      // Single line comments are file headers if they're followed by a different
+      // comment type of there's a blank line between them and the first token.
+      isFileHeader = mayBeFileHeader &&
+          (nextComment != null ||
+              _hasBlankLineBetween(end, _unit.beginToken.offset));
     }
 
-    final Token firstComment = firstToken?.precedingComments;
-    if (firstComment == null ||
-        firstComment.type != TokenType.SINGLE_LINE_COMMENT) {
-      return;
+    final kind = isFileHeader
+        ? FoldingKind.FILE_HEADER
+        : (commentToken.lexeme.startsWith('///') ||
+                commentToken.lexeme.startsWith('/**'))
+            ? FoldingKind.DOCUMENTATION_COMMENT
+            : FoldingKind.COMMENT;
+
+    _addRegion(offset, end, kind);
+
+    return nextComment;
+  }
+
+  void _addCommentRegions() {
+    var token = _unit.beginToken;
+    if (token.type == TokenType.SCRIPT_TAG) {
+      token = token.next;
     }
-
-    // Walk through the comments looking for a blank line to signal the end of
-    // the file header.
-    var lastComment = firstComment;
-    while (lastComment.next != null) {
-      lastComment = lastComment.next;
-
-      // If we ran out of tokens, use the original token as starting position.
-      final hasBlankLine =
-          _hasBlankLineBetween(lastComment, lastComment.next ?? firstToken);
-
-      // Also considered non-single-line-comments as the end
-      final nextCommentIsDifferentType = lastComment.next != null &&
-          lastComment.next.type != TokenType.SINGLE_LINE_COMMENT;
-
-      if (hasBlankLine || nextCommentIsDifferentType) {
-        _addRegion(firstComment.end, lastComment.end, FoldingKind.FILE_HEADER);
+    var isFirstToken = true;
+    while (token != null) {
+      Token commentToken = token.precedingComments;
+      while (commentToken != null) {
+        commentToken =
+            _addCommentRegion(commentToken, mayBeFileHeader: isFirstToken);
+      }
+      isFirstToken = false;
+      // Only exit the loop when hitting EOF *after* processing the token as
+      // the EOF token may have preceeding comments.
+      if (token.type == TokenType.EOF) {
         break;
       }
+      token = token.next;
     }
   }
 
@@ -114,9 +164,9 @@
     }
   }
 
-  bool _hasBlankLineBetween(Token first, Token second) {
-    final CharacterLocation firstLoc = _lineInfo.getLocation(first.end);
-    final CharacterLocation secondLoc = _lineInfo.getLocation(second.offset);
+  bool _hasBlankLineBetween(int offset, int end) {
+    final CharacterLocation firstLoc = _lineInfo.getLocation(offset);
+    final CharacterLocation secondLoc = _lineInfo.getLocation(end);
     return secondLoc.lineNumber - firstLoc.lineNumber > 1;
   }
 
@@ -162,15 +212,6 @@
   }
 
   @override
-  void visitComment(Comment node) {
-    if (node.isDocumentation) {
-      _computer._addRegion(
-          node.offset, node.end, FoldingKind.DOCUMENTATION_COMMENT);
-    }
-    super.visitComment(node);
-  }
-
-  @override
   void visitConstructorDeclaration(ConstructorDeclaration node) {
     _computer._addRegionForAnnotations(node.metadata);
     super.visitConstructorDeclaration(node);
@@ -303,3 +344,17 @@
     super.visitWhileStatement(node);
   }
 }
+
+extension _CommentTokenExtensions on Token {
+  static final _newlinePattern = RegExp(r'[\r\n]');
+
+  /// The offset of the first eol character or null
+  /// if no newlines were found.
+  int get eolOffset {
+    final offset = lexeme.indexOf(_newlinePattern);
+    return offset != -1 ? offset : null;
+  }
+
+  /// Whether this comment is a triple-slash single line comment.
+  bool get isTripleSlash => lexeme.startsWith('///');
+}
diff --git a/pkg/analysis_server/lib/src/lsp/mapping.dart b/pkg/analysis_server/lib/src/lsp/mapping.dart
index d02da72..fdde79c 100644
--- a/pkg/analysis_server/lib/src/lsp/mapping.dart
+++ b/pkg/analysis_server/lib/src/lsp/mapping.dart
@@ -1055,6 +1055,7 @@
 
 lsp.FoldingRangeKind toFoldingRangeKind(server.FoldingKind kind) {
   switch (kind) {
+    case server.FoldingKind.COMMENT:
     case server.FoldingKind.DOCUMENTATION_COMMENT:
     case server.FoldingKind.FILE_HEADER:
       return lsp.FoldingRangeKind.Comment;
diff --git a/pkg/analysis_server/test/integration/support/protocol_matchers.dart b/pkg/analysis_server/test/integration/support/protocol_matchers.dart
index de9314a..cf6abe5 100644
--- a/pkg/analysis_server/test/integration/support/protocol_matchers.dart
+++ b/pkg/analysis_server/test/integration/support/protocol_matchers.dart
@@ -723,6 +723,7 @@
 ///   ANNOTATIONS
 ///   BLOCK
 ///   CLASS_BODY
+///   COMMENT
 ///   DIRECTIVES
 ///   DOCUMENTATION_COMMENT
 ///   FILE_HEADER
@@ -734,6 +735,7 @@
   'ANNOTATIONS',
   'BLOCK',
   'CLASS_BODY',
+  'COMMENT',
   'DIRECTIVES',
   'DOCUMENTATION_COMMENT',
   'FILE_HEADER',
diff --git a/pkg/analysis_server/test/lsp/folding_test.dart b/pkg/analysis_server/test/lsp/folding_test.dart
index 1caf00a..8b4e389 100644
--- a/pkg/analysis_server/test/lsp/folding_test.dart
+++ b/pkg/analysis_server/test/lsp/folding_test.dart
@@ -44,7 +44,7 @@
 
   Future<void> test_comments() async {
     final content = '''
-    [[/// This is a comment
+    /// This is a comment[[
     /// that spans many lines]]
     class MyClass2 {}
     ''';
@@ -164,13 +164,6 @@
   }
 
   Future<void> test_headersImportsComments() async {
-    // TODO(dantup): Review why the file header and the method comment ranges
-    // are different... one spans only the range to collapse, but the other
-    // just starts at the logical block.
-    // The LSP spec doesn't give any guidance on whether the first part of
-    // the surrounded content should be visible or not after folding
-    // so we'll need to revisit this once there's clarification:
-    // https://github.com/Microsoft/language-server-protocol/issues/659
     final content = '''
     // Copyright some year by some people[[
     // See LICENCE etc.]]
@@ -178,7 +171,7 @@
     import[[ 'dart:io';
     import 'dart:async';]]
 
-    [[/// This is not the file header
+    /// This is not the file header[[
     /// It's just a comment]]
     main() {}
     ''';
diff --git a/pkg/analysis_server/test/src/computer/folding_computer_test.dart b/pkg/analysis_server/test/src/computer/folding_computer_test.dart
index 0f3fa04..03534e7 100644
--- a/pkg/analysis_server/test/src/computer/folding_computer_test.dart
+++ b/pkg/analysis_server/test/src/computer/folding_computer_test.dart
@@ -17,6 +17,12 @@
 
 @reflectiveTest
 class FoldingComputerTest extends AbstractContextTest {
+  static const commentKinds = {
+    FoldingKind.FILE_HEADER,
+    FoldingKind.COMMENT,
+    FoldingKind.DOCUMENTATION_COMMENT
+  };
+
   String sourcePath;
 
   @override
@@ -125,15 +131,72 @@
 
   Future<void> test_comment_is_not_considered_file_header() async {
     var content = """
-// This is not the file header
-// It's just a comment
+// This is not the file header/*1:EXC*/
+// It's just a comment/*1:INC:COMMENT*/
 main() {}
 """;
 
     // Since there are no region comment markers above
     // just check the length instead of the contents
     final regions = await _computeRegions(content);
-    expect(regions, hasLength(0));
+    _compareRegions(regions, content);
+  }
+
+  Future<void> test_comment_multiline() async {
+    var content = '''
+main() {
+/*/*1:EXC*/
+ * comment 1
+ *//*1:EXC:COMMENT*/
+
+/* this comment starts on the same line as delimeters/*2:EXC*/
+ * second line
+ *//*2:EXC:COMMENT*/
+}
+''';
+
+    final regions = await _computeRegions(content);
+    _compareRegions(regions, content, commentKinds);
+  }
+
+  Future<void> test_comment_singleFollowedByBlankLine() async {
+    var content = '''
+main() {
+// this is/*1:EXC*/
+// a comment/*1:INC:COMMENT*/
+/// this is not part of it
+}
+''';
+
+    final regions = await _computeRegions(content);
+    _compareRegions(regions, content, commentKinds);
+  }
+
+  Future<void> test_comment_singleFollowedByMulti() async {
+    var content = '''
+main() {
+  // this is/*1:EXC*/
+  // a comment/*1:INC:COMMENT*/
+  /* this is not part of it */
+  String foo;
+}
+''';
+
+    final regions = await _computeRegions(content);
+    _compareRegions(regions, content, commentKinds);
+  }
+
+  Future<void> test_comment_singleFollowedByTripleSlash() async {
+    var content = '''
+main() {
+// this is/*1:EXC*/
+// a comment/*1:INC:COMMENT*/
+/// this is not part of it
+}
+''';
+
+    final regions = await _computeRegions(content);
+    _compareRegions(regions, content, commentKinds);
   }
 
   Future<void> test_constructor_invocations() async {
@@ -164,7 +227,7 @@
 """;
 
     final regions = await _computeRegions(content);
-    _compareRegions(regions, content);
+    _compareRegions(regions, content, {FoldingKind.FILE_HEADER});
   }
 
   Future<void> test_file_header_does_not_include_block_comments() async {
@@ -179,7 +242,7 @@
 """;
 
     final regions = await _computeRegions(content);
-    expect(regions, hasLength(0));
+    _compareRegions(regions, content, {FoldingKind.FILE_HEADER});
   }
 
   Future<void> test_file_header_with_no_function_comment() async {
@@ -191,7 +254,7 @@
 ''';
 
     final regions = await _computeRegions(content);
-    _compareRegions(regions, content);
+    _compareRegions(regions, content, {FoldingKind.FILE_HEADER});
   }
 
   Future<void> test_file_header_with_non_end_of_line_comment() async {
@@ -204,7 +267,7 @@
 """;
 
     final regions = await _computeRegions(content);
-    _compareRegions(regions, content);
+    _compareRegions(regions, content, {FoldingKind.FILE_HEADER});
   }
 
   Future<void> test_file_header_with_script_prefix() async {
@@ -219,6 +282,19 @@
 """;
 
     final regions = await _computeRegions(content);
+    _compareRegions(regions, content, {FoldingKind.FILE_HEADER});
+  }
+
+  Future<void> test_fileHeader_singleFollowedByBlank() async {
+    var content = '''
+// this is/*1:EXC*/
+// a file header/*1:INC:FILE_HEADER*/
+
+// this is not part of it
+main() {}
+''';
+
+    final regions = await _computeRegions(content);
     _compareRegions(regions, content);
   }
 
@@ -263,7 +339,7 @@
     var content = '''
 // Content before
 
-/*1:EXC*//// This is a doc comment
+/// This is a doc comment/*1:EXC*/
 /// that spans lines/*1:INC:DOCUMENTATION_COMMENT*/
 main() {/*2:INC*/
   print("Hello, world!");
@@ -434,11 +510,19 @@
 
   /// Compares provided folding regions with expected
   /// regions extracted from the comments in the provided content.
-  void _compareRegions(List<FoldingRegion> regions, String content) {
+  ///
+  /// If [onlyKinds] is supplied only regions of that type will be compared.
+  void _compareRegions(List<FoldingRegion> regions, String content,
+      [Set<FoldingKind> onlyKinds]) {
     // Find all numeric markers for region starts.
     final regex = RegExp(r'/\*(\d+):(INC|EXC)\*/');
     final expectedRegions = regex.allMatches(content);
 
+    if (onlyKinds != null) {
+      regions =
+          regions.where((region) => onlyKinds.contains(region.kind)).toList();
+    }
+
     // Check we didn't get more than expected, since the loop below only
     // checks for the presence of matches, not absence.
     expect(regions, hasLength(expectedRegions.length));
diff --git a/pkg/analysis_server/tool/spec/generated/java/types/FoldingKind.java b/pkg/analysis_server/tool/spec/generated/java/types/FoldingKind.java
index be9f538..3b8b236 100644
--- a/pkg/analysis_server/tool/spec/generated/java/types/FoldingKind.java
+++ b/pkg/analysis_server/tool/spec/generated/java/types/FoldingKind.java
@@ -21,6 +21,8 @@
 
   public static final String CLASS_BODY = "CLASS_BODY";
 
+  public static final String COMMENT = "COMMENT";
+
   public static final String DIRECTIVES = "DIRECTIVES";
 
   public static final String DOCUMENTATION_COMMENT = "DOCUMENTATION_COMMENT";
diff --git a/pkg/analysis_server/tool/spec/spec_input.html b/pkg/analysis_server/tool/spec/spec_input.html
index 47b9e8f..7d118ec 100644
--- a/pkg/analysis_server/tool/spec/spec_input.html
+++ b/pkg/analysis_server/tool/spec/spec_input.html
@@ -7,7 +7,7 @@
 <body>
 <h1>Analysis Server API Specification</h1>
 <h1 style="color:#999999">Version
-  <version>1.32.1</version>
+  <version>1.32.2</version>
 </h1>
 <p>
   This document contains a specification of the API provided by the
@@ -134,9 +134,13 @@
   ignoring the item or treating it with some default/fallback handling.
 </p>
 <h3>Changelog</h3>
+<h4>1.32.2</h4>
+<ul>
+  <li>Added <tt>FoldingKind.COMMENT</tt> for folding regions for blocks of comments.</li>
+</ul>
 <h4>1.32.1</h4>
 <ul>
-  <li>Added <tt>CompletionSuggestionKind.PACKAGE_NAME</tt> for Pub package name completions in <tt>pubspec.yaml</tt></li>
+  <li>Added <tt>CompletionSuggestionKind.PACKAGE_NAME</tt> for Pub package name completions in <tt>pubspec.yaml</tt>.</li>
 </ul>
 <h3>Domains</h3>
 <p>
diff --git a/pkg/analysis_server_client/lib/src/protocol/protocol_common.dart b/pkg/analysis_server_client/lib/src/protocol/protocol_common.dart
index 346847c..2b61453 100644
--- a/pkg/analysis_server_client/lib/src/protocol/protocol_common.dart
+++ b/pkg/analysis_server_client/lib/src/protocol/protocol_common.dart
@@ -1904,6 +1904,7 @@
 ///   ANNOTATIONS
 ///   BLOCK
 ///   CLASS_BODY
+///   COMMENT
 ///   DIRECTIVES
 ///   DOCUMENTATION_COMMENT
 ///   FILE_HEADER
@@ -1920,6 +1921,8 @@
 
   static const FoldingKind CLASS_BODY = FoldingKind._('CLASS_BODY');
 
+  static const FoldingKind COMMENT = FoldingKind._('COMMENT');
+
   static const FoldingKind DIRECTIVES = FoldingKind._('DIRECTIVES');
 
   static const FoldingKind DOCUMENTATION_COMMENT =
@@ -1938,6 +1941,7 @@
     ANNOTATIONS,
     BLOCK,
     CLASS_BODY,
+    COMMENT,
     DIRECTIVES,
     DOCUMENTATION_COMMENT,
     FILE_HEADER,
@@ -1959,6 +1963,8 @@
         return BLOCK;
       case 'CLASS_BODY':
         return CLASS_BODY;
+      case 'COMMENT':
+        return COMMENT;
       case 'DIRECTIVES':
         return DIRECTIVES;
       case 'DOCUMENTATION_COMMENT':
diff --git a/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart b/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart
index 4ad5e63..68ea10f 100644
--- a/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart
+++ b/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart
@@ -6,7 +6,7 @@
 // To regenerate the file, use the script
 // "pkg/analysis_server/tool/spec/generate_files".
 
-const String PROTOCOL_VERSION = '1.32.1';
+const String PROTOCOL_VERSION = '1.32.2';
 
 const String ANALYSIS_NOTIFICATION_ANALYZED_FILES = 'analysis.analyzedFiles';
 const String ANALYSIS_NOTIFICATION_ANALYZED_FILES_DIRECTORIES = 'directories';
diff --git a/pkg/analyzer_plugin/doc/api.html b/pkg/analyzer_plugin/doc/api.html
index 6aaac51..dd53e3b 100644
--- a/pkg/analyzer_plugin/doc/api.html
+++ b/pkg/analyzer_plugin/doc/api.html
@@ -1309,7 +1309,7 @@
       An enumeration of the kinds of folding regions.
     </p>
     
-  <dl><dt class="value">ANNOTATIONS</dt><dt class="value">BLOCK</dt><dt class="value">CLASS_BODY</dt><dt class="value">DIRECTIVES</dt><dt class="value">DOCUMENTATION_COMMENT</dt><dt class="value">FILE_HEADER</dt><dt class="value">FUNCTION_BODY</dt><dt class="value">INVOCATION</dt><dt class="value">LITERAL</dt></dl></dd><dt class="typeDefinition"><a name="type_FoldingRegion">FoldingRegion: object</a></dt><dd>
+  <dl><dt class="value">ANNOTATIONS</dt><dt class="value">BLOCK</dt><dt class="value">CLASS_BODY</dt><dt class="value">COMMENT</dt><dt class="value">DIRECTIVES</dt><dt class="value">DOCUMENTATION_COMMENT</dt><dt class="value">FILE_HEADER</dt><dt class="value">FUNCTION_BODY</dt><dt class="value">INVOCATION</dt><dt class="value">LITERAL</dt></dl></dd><dt class="typeDefinition"><a name="type_FoldingRegion">FoldingRegion: object</a></dt><dd>
     <p>
       A description of a region that can be folded.
     </p>
diff --git a/pkg/analyzer_plugin/lib/protocol/protocol_common.dart b/pkg/analyzer_plugin/lib/protocol/protocol_common.dart
index 56bcadd..89b8f94 100644
--- a/pkg/analyzer_plugin/lib/protocol/protocol_common.dart
+++ b/pkg/analyzer_plugin/lib/protocol/protocol_common.dart
@@ -1904,6 +1904,7 @@
 ///   ANNOTATIONS
 ///   BLOCK
 ///   CLASS_BODY
+///   COMMENT
 ///   DIRECTIVES
 ///   DOCUMENTATION_COMMENT
 ///   FILE_HEADER
@@ -1920,6 +1921,8 @@
 
   static const FoldingKind CLASS_BODY = FoldingKind._('CLASS_BODY');
 
+  static const FoldingKind COMMENT = FoldingKind._('COMMENT');
+
   static const FoldingKind DIRECTIVES = FoldingKind._('DIRECTIVES');
 
   static const FoldingKind DOCUMENTATION_COMMENT =
@@ -1938,6 +1941,7 @@
     ANNOTATIONS,
     BLOCK,
     CLASS_BODY,
+    COMMENT,
     DIRECTIVES,
     DOCUMENTATION_COMMENT,
     FILE_HEADER,
@@ -1959,6 +1963,8 @@
         return BLOCK;
       case 'CLASS_BODY':
         return CLASS_BODY;
+      case 'COMMENT':
+        return COMMENT;
       case 'DIRECTIVES':
         return DIRECTIVES;
       case 'DOCUMENTATION_COMMENT':
diff --git a/pkg/analyzer_plugin/test/integration/support/protocol_matchers.dart b/pkg/analyzer_plugin/test/integration/support/protocol_matchers.dart
index d495c4f..3ac543a 100644
--- a/pkg/analyzer_plugin/test/integration/support/protocol_matchers.dart
+++ b/pkg/analyzer_plugin/test/integration/support/protocol_matchers.dart
@@ -305,6 +305,7 @@
 ///   ANNOTATIONS
 ///   BLOCK
 ///   CLASS_BODY
+///   COMMENT
 ///   DIRECTIVES
 ///   DOCUMENTATION_COMMENT
 ///   FILE_HEADER
@@ -316,6 +317,7 @@
   'ANNOTATIONS',
   'BLOCK',
   'CLASS_BODY',
+  'COMMENT',
   'DIRECTIVES',
   'DOCUMENTATION_COMMENT',
   'FILE_HEADER',
diff --git a/pkg/analyzer_plugin/tool/spec/common_types_spec.html b/pkg/analyzer_plugin/tool/spec/common_types_spec.html
index b1c7056..df7fa1c 100644
--- a/pkg/analyzer_plugin/tool/spec/common_types_spec.html
+++ b/pkg/analyzer_plugin/tool/spec/common_types_spec.html
@@ -6,7 +6,7 @@
 </head>
 <body>
 <h1>Common Types</h1>
-<version>1.4.0</version>
+<version>1.4.1</version>
 <p>
   This document contains a specification of the types that are common between
   the analysis server wire protocol and the analysis server plugin wire
@@ -575,6 +575,7 @@
       <value><code>ANNOTATIONS</code></value>
       <value><code>BLOCK</code></value>
       <value><code>CLASS_BODY</code></value>
+      <value><code>COMMENT</code></value>
       <value><code>DIRECTIVES</code></value>
       <value><code>DOCUMENTATION_COMMENT</code></value>
       <value><code>FILE_HEADER</code></value>
diff --git a/pkg/dartdev/test/commands/compile_test.dart b/pkg/dartdev/test/commands/compile_test.dart
index 0921ddd..44a3ab9 100644
--- a/pkg/dartdev/test/commands/compile_test.dart
+++ b/pkg/dartdev/test/commands/compile_test.dart
@@ -17,7 +17,7 @@
 
 const String soundNullSafetyMessage = 'Info: Compiling with sound null safety';
 const String unsoundNullSafetyMessage =
-    'Info: Compiling with unsound null safety';
+    'Info: Compiling without sound null safety';
 
 void defineCompileTests() {
   // *** NOTE ***: These tests *must* be run with the `--use-sdk` option
diff --git a/pkg/front_end/lib/src/base/processed_options.dart b/pkg/front_end/lib/src/base/processed_options.dart
index 84f9e63..e768713 100644
--- a/pkg/front_end/lib/src/base/processed_options.dart
+++ b/pkg/front_end/lib/src/base/processed_options.dart
@@ -48,7 +48,7 @@
         messageCantInferPackagesFromManyInputs,
         messageCantInferPackagesFromPackageUri,
         messageCompilingWithSoundNullSafety,
-        messageCompilingWithUnsoundNullSafety,
+        messageCompilingWithoutSoundNullSafety,
         messageInternalProblemProvidedBothCompileSdkAndSdkSummary,
         messageMissingInput,
         noLength,
@@ -277,8 +277,8 @@
     if (_raw.invocationModes.contains(InvocationMode.compile)) {
       switch (nnbdMode) {
         case NnbdMode.Weak:
-          reportWithoutLocation(messageCompilingWithUnsoundNullSafety,
-              messageCompilingWithUnsoundNullSafety.severity);
+          reportWithoutLocation(messageCompilingWithoutSoundNullSafety,
+              messageCompilingWithoutSoundNullSafety.severity);
           break;
         case NnbdMode.Strong:
           reportWithoutLocation(messageCompilingWithSoundNullSafety,
diff --git a/pkg/front_end/lib/src/fasta/builder/constructor_builder.dart b/pkg/front_end/lib/src/fasta/builder/constructor_builder.dart
index e962880..b98d56b 100644
--- a/pkg/front_end/lib/src/fasta/builder/constructor_builder.dart
+++ b/pkg/front_end/lib/src/fasta/builder/constructor_builder.dart
@@ -140,7 +140,7 @@
       int charOffset,
       this.charOpenParenOffset,
       int charEndOffset,
-      Constructor referenceFrom,
+      Member referenceFrom,
       [String nativeMethodName])
       : _constructor = new Constructor(null,
             fileUri: compilationUnit.fileUri,
diff --git a/pkg/front_end/lib/src/fasta/source/source_library_builder.dart b/pkg/front_end/lib/src/fasta/source/source_library_builder.dart
index 7a2103d..c74e46a 100644
--- a/pkg/front_end/lib/src/fasta/source/source_library_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_library_builder.dart
@@ -2258,7 +2258,7 @@
       String nativeMethodName,
       {Token beginInitializers}) {
     MetadataCollector metadataCollector = loader.target.metadataCollector;
-    Constructor referenceFrom;
+    Member referenceFrom;
     if (_currentClassReferencesFromIndexed != null) {
       referenceFrom = _currentClassReferencesFromIndexed.lookupConstructor(
           new Name(
@@ -2438,9 +2438,10 @@
       procedureName = name;
     }
 
-    Reference reference =
-        _currentClassReferencesFromIndexed?.lookupGetterReference(new Name(
-            procedureName, _currentClassReferencesFromIndexed.library));
+    Reference reference = _currentClassReferencesFromIndexed
+        ?.lookupConstructor(
+            new Name(procedureName, _currentClassReferencesFromIndexed.library))
+        ?.reference;
 
     ProcedureBuilder procedureBuilder;
     if (redirectionTarget != null) {
diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status
index 794f307..245cd2d 100644
--- a/pkg/front_end/messages.status
+++ b/pkg/front_end/messages.status
@@ -70,7 +70,7 @@
 ClassInNullAwareReceiver/analyzerCode: Fail
 ColonInPlaceOfIn/example: Fail
 CompilingWithSoundNullSafety/analyzerCode: Fail
-CompilingWithUnsoundNullSafety/analyzerCode: Fail
+CompilingWithoutSoundNullSafety/analyzerCode: Fail
 ConflictingModifiers/part_wrapped_script1: Fail
 ConflictingModifiers/script1: Fail
 ConflictsWithConstructor/example: Fail
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index 5c8b909..c06e19b 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -4940,8 +4940,8 @@
   script: |
     main() {}
 
-CompilingWithUnsoundNullSafety:
-  template: "Compiling with unsound null safety"
+CompilingWithoutSoundNullSafety:
+  template: "Compiling without sound null safety"
   configuration: nnbd-weak,compile
   severity: INFO
   script: |
diff --git a/pkg/front_end/test/crashing_test_case_minimizer_impl.dart b/pkg/front_end/test/crashing_test_case_minimizer_impl.dart
index 6e8344c..abc52b9 100644
--- a/pkg/front_end/test/crashing_test_case_minimizer_impl.dart
+++ b/pkg/front_end/test/crashing_test_case_minimizer_impl.dart
@@ -53,7 +53,7 @@
 import 'package:front_end/src/fasta/util/textual_outline.dart'
     show textualOutline;
 
-import 'package:kernel/ast.dart' show Component;
+import 'package:kernel/ast.dart' show Component, LibraryPart;
 
 import 'package:kernel/binary/ast_to_binary.dart' show BinaryPrinter;
 
@@ -1746,8 +1746,9 @@
   }
 
   bool _isUriNnbd(Uri uri) {
+    Uri asImportUri = _getImportUri(uri);
     LibraryBuilder libraryBuilder = _latestCrashingIncrementalCompiler
-        .userCode.loader.builders[_getImportUri(uri)];
+        .userCode.loader.builders[asImportUri];
     if (libraryBuilder != null) {
       return libraryBuilder.isNonNullableByDefault;
     }
@@ -1755,12 +1756,18 @@
     for (LibraryBuilder libraryBuilder
         in _latestCrashingIncrementalCompiler.userCode.loader.builders.values) {
       if (libraryBuilder.importUri == uri) {
-        print("Found $uri as ${libraryBuilder.importUri} "
-            "(!= ${_getImportUri(uri)})");
+        print("Found $uri as ${libraryBuilder.importUri} (!= ${asImportUri})");
         return libraryBuilder.isNonNullableByDefault;
       }
+      // Check parts too.
+      for (LibraryPart part in libraryBuilder.library.parts) {
+        Uri thisPartUri = libraryBuilder.importUri.resolve(part.partUri);
+        if (thisPartUri == uri || thisPartUri == asImportUri) {
+          print("Found $uri as part of ${libraryBuilder.importUri}");
+          return libraryBuilder.isNonNullableByDefault;
+        }
+      }
     }
-    // This might be parts?
     throw "Couldn't lookup $uri at all!";
   }
 
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_45_flutter.yaml b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_45_flutter.yaml
new file mode 100644
index 0000000..7ce01e9f
--- /dev/null
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_45_flutter.yaml
@@ -0,0 +1,31 @@
+# Copyright (c) 2021, 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.md file.
+
+# Reproduce a crash.
+
+type: newworld
+trackWidgetCreation: true
+target: VM
+worlds:
+  - entry: main.dart
+    experiments: alternative-invalidation-strategy
+    sources:
+      main.dart: |
+        class Foo {
+          factory Foo.bar() {
+            return null;
+          }
+          void bar() {}
+        }
+    expectedLibraryCount: 1
+
+  - entry: main.dart
+    experiments: alternative-invalidation-strategy
+    worldType: updated
+    expectInitializeFromDill: false
+    invalidate:
+      - main.dart
+    expectedLibraryCount: 1
+    expectsRebuildBodiesOnly: true
+
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_45_flutter.yaml.world.1.expect b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_45_flutter.yaml.world.1.expect
new file mode 100644
index 0000000..3416529
--- /dev/null
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_45_flutter.yaml.world.1.expect
@@ -0,0 +1,20 @@
+main = <No Member>;
+library from "org-dartlang-test:///main.dart" as main {
+
+  class Foo extends dart.core::Object {
+    static factory bar() → main::Foo* {
+      return null;
+    }
+    method bar() → void {}
+    abstract member-signature get _identityHashCode() → dart.core::int*; -> dart.core::Object::_identityHashCode
+    abstract member-signature method _instanceOf(dynamic instantiatorTypeArguments, dynamic functionTypeArguments, dynamic type) → dart.core::bool*; -> dart.core::Object::_instanceOf
+    abstract member-signature method _simpleInstanceOf(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOf
+    abstract member-signature method _simpleInstanceOfTrue(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOfTrue
+    abstract member-signature method _simpleInstanceOfFalse(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOfFalse
+    abstract member-signature operator ==(dynamic other) → dart.core::bool*; -> dart.core::Object::==
+    abstract member-signature get hashCode() → dart.core::int*; -> dart.core::Object::hashCode
+    abstract member-signature method toString() → dart.core::String*; -> dart.core::Object::toString
+    abstract member-signature method noSuchMethod(dart.core::Invocation* invocation) → dynamic; -> dart.core::Object::noSuchMethod
+    abstract member-signature get runtimeType() → dart.core::Type*; -> dart.core::Object::runtimeType
+  }
+}
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_45_flutter.yaml.world.2.expect b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_45_flutter.yaml.world.2.expect
new file mode 100644
index 0000000..3416529
--- /dev/null
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_45_flutter.yaml.world.2.expect
@@ -0,0 +1,20 @@
+main = <No Member>;
+library from "org-dartlang-test:///main.dart" as main {
+
+  class Foo extends dart.core::Object {
+    static factory bar() → main::Foo* {
+      return null;
+    }
+    method bar() → void {}
+    abstract member-signature get _identityHashCode() → dart.core::int*; -> dart.core::Object::_identityHashCode
+    abstract member-signature method _instanceOf(dynamic instantiatorTypeArguments, dynamic functionTypeArguments, dynamic type) → dart.core::bool*; -> dart.core::Object::_instanceOf
+    abstract member-signature method _simpleInstanceOf(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOf
+    abstract member-signature method _simpleInstanceOfTrue(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOfTrue
+    abstract member-signature method _simpleInstanceOfFalse(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOfFalse
+    abstract member-signature operator ==(dynamic other) → dart.core::bool*; -> dart.core::Object::==
+    abstract member-signature get hashCode() → dart.core::int*; -> dart.core::Object::hashCode
+    abstract member-signature method toString() → dart.core::String*; -> dart.core::Object::toString
+    abstract member-signature method noSuchMethod(dart.core::Invocation* invocation) → dynamic; -> dart.core::Object::noSuchMethod
+    abstract member-signature get runtimeType() → dart.core::Type*; -> dart.core::Object::runtimeType
+  }
+}
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_45_flutter_prime_1.yaml b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_45_flutter_prime_1.yaml
new file mode 100644
index 0000000..8b31333
--- /dev/null
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_45_flutter_prime_1.yaml
@@ -0,0 +1,31 @@
+# Copyright (c) 2021, 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.md file.
+
+# Reproduce a crash.
+
+type: newworld
+trackWidgetCreation: true
+target: VM
+worlds:
+  - entry: main.dart
+    experiments: alternative-invalidation-strategy
+    sources:
+      main.dart: |
+        class Foo {
+          Foo();
+          factory Foo.bar() = Baz;
+          void bar() {}
+        }
+        class Baz extends Foo {}
+    expectedLibraryCount: 1
+
+  - entry: main.dart
+    experiments: alternative-invalidation-strategy
+    worldType: updated
+    expectInitializeFromDill: false
+    invalidate:
+      - main.dart
+    expectedLibraryCount: 1
+    expectsRebuildBodiesOnly: true
+
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_45_flutter_prime_1.yaml.world.1.expect b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_45_flutter_prime_1.yaml.world.1.expect
new file mode 100644
index 0000000..47fdc2b
--- /dev/null
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_45_flutter_prime_1.yaml.world.1.expect
@@ -0,0 +1,28 @@
+main = <No Member>;
+library from "org-dartlang-test:///main.dart" as main {
+
+  class Foo extends dart.core::Object {
+    static final field dynamic _redirecting# = <dynamic>[main::Foo::bar];
+    constructor •() → main::Foo*
+      : super dart.core::Object::•()
+      ;
+    static factory bar() → main::Foo*
+      let dynamic #redirecting_factory = main::Baz::• in invalid-expression;
+    method bar() → void {}
+    abstract member-signature get _identityHashCode() → dart.core::int*; -> dart.core::Object::_identityHashCode
+    abstract member-signature method _instanceOf(dynamic instantiatorTypeArguments, dynamic functionTypeArguments, dynamic type) → dart.core::bool*; -> dart.core::Object::_instanceOf
+    abstract member-signature method _simpleInstanceOf(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOf
+    abstract member-signature method _simpleInstanceOfTrue(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOfTrue
+    abstract member-signature method _simpleInstanceOfFalse(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOfFalse
+    abstract member-signature operator ==(dynamic other) → dart.core::bool*; -> dart.core::Object::==
+    abstract member-signature get hashCode() → dart.core::int*; -> dart.core::Object::hashCode
+    abstract member-signature method toString() → dart.core::String*; -> dart.core::Object::toString
+    abstract member-signature method noSuchMethod(dart.core::Invocation* invocation) → dynamic; -> dart.core::Object::noSuchMethod
+    abstract member-signature get runtimeType() → dart.core::Type*; -> dart.core::Object::runtimeType
+  }
+  class Baz extends main::Foo {
+    synthetic constructor •() → main::Baz*
+      : super main::Foo::•()
+      ;
+  }
+}
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_45_flutter_prime_1.yaml.world.2.expect b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_45_flutter_prime_1.yaml.world.2.expect
new file mode 100644
index 0000000..47fdc2b
--- /dev/null
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_45_flutter_prime_1.yaml.world.2.expect
@@ -0,0 +1,28 @@
+main = <No Member>;
+library from "org-dartlang-test:///main.dart" as main {
+
+  class Foo extends dart.core::Object {
+    static final field dynamic _redirecting# = <dynamic>[main::Foo::bar];
+    constructor •() → main::Foo*
+      : super dart.core::Object::•()
+      ;
+    static factory bar() → main::Foo*
+      let dynamic #redirecting_factory = main::Baz::• in invalid-expression;
+    method bar() → void {}
+    abstract member-signature get _identityHashCode() → dart.core::int*; -> dart.core::Object::_identityHashCode
+    abstract member-signature method _instanceOf(dynamic instantiatorTypeArguments, dynamic functionTypeArguments, dynamic type) → dart.core::bool*; -> dart.core::Object::_instanceOf
+    abstract member-signature method _simpleInstanceOf(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOf
+    abstract member-signature method _simpleInstanceOfTrue(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOfTrue
+    abstract member-signature method _simpleInstanceOfFalse(dynamic type) → dart.core::bool*; -> dart.core::Object::_simpleInstanceOfFalse
+    abstract member-signature operator ==(dynamic other) → dart.core::bool*; -> dart.core::Object::==
+    abstract member-signature get hashCode() → dart.core::int*; -> dart.core::Object::hashCode
+    abstract member-signature method toString() → dart.core::String*; -> dart.core::Object::toString
+    abstract member-signature method noSuchMethod(dart.core::Invocation* invocation) → dynamic; -> dart.core::Object::noSuchMethod
+    abstract member-signature get runtimeType() → dart.core::Type*; -> dart.core::Object::runtimeType
+  }
+  class Baz extends main::Foo {
+    synthetic constructor •() → main::Baz*
+      : super main::Foo::•()
+      ;
+  }
+}
diff --git a/pkg/kernel/lib/reference_from_index.dart b/pkg/kernel/lib/reference_from_index.dart
index 0a2ca57..02efc1f 100644
--- a/pkg/kernel/lib/reference_from_index.dart
+++ b/pkg/kernel/lib/reference_from_index.dart
@@ -11,9 +11,11 @@
         Extension,
         Field,
         Library,
+        Member,
         Name,
-        Reference,
         Procedure,
+        ProcedureKind,
+        Reference,
         Typedef;
 
 class ReferenceFromIndex {
@@ -41,15 +43,21 @@
 
   void _addProcedures(List<Procedure> procedures) {
     for (int i = 0; i < procedures.length; i++) {
-      Procedure procedure = procedures[i];
-      Name name = procedure.name;
-      if (procedure.isSetter) {
-        assert(_setterReferences[name] == null);
-        _setterReferences[name] = procedure.reference;
-      } else {
-        assert(_getterReferences[name] == null);
-        _getterReferences[name] = procedure.reference;
-      }
+      _addProcedure(procedures[i]);
+    }
+  }
+
+  void _addProcedure(Procedure procedure) {
+    Name name = procedure.name;
+    if (procedure.isSetter) {
+      assert(_setterReferences[name] == null);
+      _setterReferences[name] = procedure.reference;
+    } else {
+      assert(_getterReferences[name] == null);
+      assert(procedure.kind == ProcedureKind.Method ||
+          procedure.kind == ProcedureKind.Getter ||
+          procedure.kind == ProcedureKind.Operator);
+      _getterReferences[name] = procedure.reference;
     }
   }
 
@@ -104,7 +112,7 @@
 }
 
 class IndexedClass extends IndexedContainer {
-  final Map<Name, Constructor> _constructors = new Map<Name, Constructor>();
+  final Map<Name, Member> _constructors = new Map<Name, Member>();
   final Library library;
 
   IndexedClass._(Class c, this.library) {
@@ -112,9 +120,16 @@
       Constructor constructor = c.constructors[i];
       _constructors[constructor.name] = constructor;
     }
-    _addProcedures(c.procedures);
+    for (int i = 0; i < c.procedures.length; i++) {
+      Procedure procedure = c.procedures[i];
+      if (procedure.isFactory) {
+        _constructors[procedure.name] = procedure;
+      } else {
+        _addProcedure(procedure);
+      }
+    }
     _addFields(c.fields);
   }
 
-  Constructor lookupConstructor(Name name) => _constructors[name];
+  Member lookupConstructor(Name name) => _constructors[name];
 }
diff --git a/pkg/nnbd_migration/lib/src/edge_builder.dart b/pkg/nnbd_migration/lib/src/edge_builder.dart
index 2dc17ea..2213ef3 100644
--- a/pkg/nnbd_migration/lib/src/edge_builder.dart
+++ b/pkg/nnbd_migration/lib/src/edge_builder.dart
@@ -231,6 +231,10 @@
   final Map<MethodInvocation, DecoratedType Function(DecoratedType)>
       _deferredMethodInvocationProcessing = {};
 
+  /// If we are visiting a local function or closure, the set of local variables
+  /// assigned to so far inside it.  Otherwise `null`.
+  Set<Element> _elementsWrittenToInLocalFunction;
+
   EdgeBuilder(this.typeProvider, this._typeSystem, this._variables, this._graph,
       this.source, this.listener, this._decoratedClassHierarchy,
       {this.instrumentation})
@@ -857,12 +861,21 @@
     if (_flowAnalysis != null) {
       // This is a local function.
       var previousPostDominatedLocals = _postDominatedLocals;
+      var previousElementsWrittenToInLocalFunction =
+          _elementsWrittenToInLocalFunction;
       try {
+        _elementsWrittenToInLocalFunction = {};
         _postDominatedLocals = _ScopedLocalSet();
         _flowAnalysis.functionExpression_begin(node);
         _dispatch(node.functionExpression);
         _flowAnalysis.functionExpression_end();
       } finally {
+        for (var element in _elementsWrittenToInLocalFunction) {
+          previousElementsWrittenToInLocalFunction?.add(element);
+          previousPostDominatedLocals.removeFromAllScopes(element);
+        }
+        _elementsWrittenToInLocalFunction =
+            previousElementsWrittenToInLocalFunction;
         _postDominatedLocals = previousPostDominatedLocals;
       }
     } else {
@@ -911,7 +924,12 @@
     _currentFunctionType =
         _variables.decoratedElementType(node.declaredElement);
     var previousPostDominatedLocals = _postDominatedLocals;
+    var previousElementsWrittenToInLocalFunction =
+        _elementsWrittenToInLocalFunction;
     try {
+      if (node.parent is! FunctionDeclaration) {
+        _elementsWrittenToInLocalFunction = {};
+      }
       _postDominatedLocals = _ScopedLocalSet();
       _postDominatedLocals.doScoped(
           elements: node.declaredElement.parameters,
@@ -921,6 +939,12 @@
     } finally {
       if (node.parent is! FunctionDeclaration) {
         _flowAnalysis.functionExpression_end();
+        for (var element in _elementsWrittenToInLocalFunction) {
+          previousElementsWrittenToInLocalFunction?.add(element);
+          previousPostDominatedLocals.removeFromAllScopes(element);
+        }
+        _elementsWrittenToInLocalFunction =
+            previousElementsWrittenToInLocalFunction;
       }
       _currentFunctionType = previousFunctionType;
       _currentFunctionExpression = previousFunction;
@@ -2373,7 +2397,11 @@
       }
     }
     if (destinationExpression != null) {
-      _postDominatedLocals.removeReferenceFromAllScopes(destinationExpression);
+      var element = _postDominatedLocals
+          .removeReferenceFromAllScopes(destinationExpression);
+      if (element != null) {
+        _elementsWrittenToInLocalFunction?.add(element);
+      }
     }
     return sourceType;
   }
@@ -3639,11 +3667,16 @@
     return false;
   }
 
-  void removeReferenceFromAllScopes(Expression expression) {
+  /// If [expression] references an element, removes that element from all
+  /// scopes and returns it.  Otherwise returns `null`.
+  Element removeReferenceFromAllScopes(Expression expression) {
     expression = expression.unParenthesized;
     if (expression is SimpleIdentifier) {
       var element = expression.staticElement;
       removeFromAllScopes(element);
+      return element;
+    } else {
+      return null;
     }
   }
 }
diff --git a/pkg/nnbd_migration/test/api_test.dart b/pkg/nnbd_migration/test/api_test.dart
index 4006512..2ff78bf 100644
--- a/pkg/nnbd_migration/test/api_test.dart
+++ b/pkg/nnbd_migration/test/api_test.dart
@@ -4591,6 +4591,80 @@
     await _checkSingleFileChanges(content, expected);
   }
 
+  Future<void> test_local_function_expression_inhibits_non_null_intent() async {
+    var content = '''
+void call(void Function() callback) {
+  callback();
+}
+test(int i, int j) {
+  call(() {
+    i = j;
+  });
+  print(i + 1);
+}
+main() {
+  test(null, 0);
+}
+''';
+    // `print(i + 1)` does *not* demonstrate non-null intent for `i` because it
+    // is write captured by the local function expression, so it's not
+    // guaranteed that a null value of `i` on entry to the function will lead to
+    // an exception.
+    var expected = '''
+void call(void Function() callback) {
+  callback();
+}
+test(int? i, int j) {
+  call(() {
+    i = j;
+  });
+  print(i! + 1);
+}
+main() {
+  test(null, 0);
+}
+''';
+    await _checkSingleFileChanges(content, expected);
+  }
+
+  Future<void> test_local_function_inhibits_non_null_intent() async {
+    var content = '''
+void call(void Function() callback) {
+  callback();
+}
+test(int i, int j) {
+  void f() {
+    i = j;
+  }
+  call(f);
+  print(i + 1);
+}
+main() {
+  test(null, 0);
+}
+''';
+    // `print(i + 1)` does *not* demonstrate non-null intent for `i` because it
+    // is write captured by the local function expression, so it's not
+    // guaranteed that a null value of `i` on entry to the function will lead to
+    // an exception.
+    var expected = '''
+void call(void Function() callback) {
+  callback();
+}
+test(int? i, int j) {
+  void f() {
+    i = j;
+  }
+  call(f);
+  print(i! + 1);
+}
+main() {
+  test(null, 0);
+}
+''';
+    await _checkSingleFileChanges(content, expected);
+  }
+
   Future<void> test_local_function_return() async {
     var content = '''
 void test({String foo}) async {
diff --git a/pkg/nnbd_migration/test/edge_builder_test.dart b/pkg/nnbd_migration/test/edge_builder_test.dart
index 1885167..e943de9 100644
--- a/pkg/nnbd_migration/test/edge_builder_test.dart
+++ b/pkg/nnbd_migration/test/edge_builder_test.dart
@@ -6197,6 +6197,58 @@
         assertEdge(decoratedTypeAnnotation('C c').node, never, hard: true));
   }
 
+  Future<void> test_postDominators_subFunction_nested() async {
+    await analyze('''
+f1(int a, int b, int c, int d) {
+  f2() {
+    a = 0;
+    f3() {
+      b = 0;
+    }
+    c = 0;
+  }
+  a + 1;
+  b + 1;
+  c + 1;
+  d + 1;
+}
+''');
+    // a, b, and c may all be written to prior to their use, so their use sites
+    // don't demonstrate non-null intent.
+    assertEdge(decoratedTypeAnnotation('int a').node, never, hard: false);
+    assertEdge(decoratedTypeAnnotation('int b').node, never, hard: false);
+    assertEdge(decoratedTypeAnnotation('int c').node, never, hard: false);
+    // However, the use of `d` does demonstrate non-null intent because there's
+    // no write to `d`.
+    assertEdge(decoratedTypeAnnotation('int d').node, never, hard: true);
+  }
+
+  Future<void> test_postDominators_subFunction_nested_closure() async {
+    await analyze('''
+f1(int a, int b, int c, int d) {
+  var f2 = () {
+    a = 0;
+    var f3 = () {
+      b = 0;
+    };
+    c = 0;
+  };
+  a + 1;
+  b + 1;
+  c + 1;
+  d + 1;
+}
+''');
+    // a, b, and c may all be written to prior to their use, so their use sites
+    // don't demonstrate non-null intent.
+    assertEdge(decoratedTypeAnnotation('int a').node, never, hard: false);
+    assertEdge(decoratedTypeAnnotation('int b').node, never, hard: false);
+    assertEdge(decoratedTypeAnnotation('int c').node, never, hard: false);
+    // However, the use of `d` does demonstrate non-null intent because there's
+    // no write to `d`.
+    assertEdge(decoratedTypeAnnotation('int d').node, never, hard: true);
+  }
+
   Future<void> test_postDominators_ternaryOperator() async {
     await analyze('''
 class C {
diff --git a/sdk/lib/io/secure_socket.dart b/sdk/lib/io/secure_socket.dart
index e5a757f..185b116 100644
--- a/sdk/lib/io/secure_socket.dart
+++ b/sdk/lib/io/secure_socket.dart
@@ -882,69 +882,70 @@
     }
   }
 
-  Future<void> _scheduleFilter() async {
+  Future<void> _scheduleFilter() {
     _filterPending = true;
     return _tryFilter();
   }
 
   Future<void> _tryFilter() async {
-    if (_status == closedStatus) {
-      return;
-    }
-    if (!_filterPending || _filterActive) {
-      return;
-    }
-    _filterActive = true;
-    _filterPending = false;
-
     try {
-      _filterStatus = await _pushAllFilterStages();
-      _filterActive = false;
-      if (_status == closedStatus) {
-        _secureFilter!.destroy();
-        _secureFilter = null;
-        return;
-      }
-      _socket.readEventsEnabled = true;
-      if (_filterStatus.writeEmpty && _closedWrite && !_socketClosedWrite) {
-        // Checks for and handles all cases of partially closed sockets.
-        shutdown(SocketDirection.send);
+      while (true) {
         if (_status == closedStatus) {
           return;
         }
-      }
-      if (_filterStatus.readEmpty && _socketClosedRead && !_closedRead) {
-        if (_status == handshakeStatus) {
-          _secureFilter!.handshake();
-          if (_status == handshakeStatus) {
-            throw new HandshakeException(
-                'Connection terminated during handshake');
+        if (!_filterPending || _filterActive) {
+          return;
+        }
+        _filterActive = true;
+        _filterPending = false;
+
+        _filterStatus = await _pushAllFilterStages();
+        _filterActive = false;
+        if (_status == closedStatus) {
+          _secureFilter!.destroy();
+          _secureFilter = null;
+          return;
+        }
+        _socket.readEventsEnabled = true;
+        if (_filterStatus.writeEmpty && _closedWrite && !_socketClosedWrite) {
+          // Checks for and handles all cases of partially closed sockets.
+          shutdown(SocketDirection.send);
+          if (_status == closedStatus) {
+            return;
           }
         }
-        _closeHandler();
-      }
-      if (_status == closedStatus) {
-        return;
-      }
-      if (_filterStatus.progress) {
-        _filterPending = true;
-        if (_filterStatus.writeEncryptedNoLongerEmpty) {
-          _writeSocket();
+        if (_filterStatus.readEmpty && _socketClosedRead && !_closedRead) {
+          if (_status == handshakeStatus) {
+            _secureFilter!.handshake();
+            if (_status == handshakeStatus) {
+              throw new HandshakeException(
+                  'Connection terminated during handshake');
+            }
+          }
+          _closeHandler();
         }
-        if (_filterStatus.writePlaintextNoLongerFull) {
-          _sendWriteEvent();
+        if (_status == closedStatus) {
+          return;
         }
-        if (_filterStatus.readEncryptedNoLongerFull) {
-          _readSocket();
-        }
-        if (_filterStatus.readPlaintextNoLongerEmpty) {
-          _scheduleReadEvent();
-        }
-        if (_status == handshakeStatus) {
-          await _secureHandshake();
+        if (_filterStatus.progress) {
+          _filterPending = true;
+          if (_filterStatus.writeEncryptedNoLongerEmpty) {
+            _writeSocket();
+          }
+          if (_filterStatus.writePlaintextNoLongerFull) {
+            _sendWriteEvent();
+          }
+          if (_filterStatus.readEncryptedNoLongerFull) {
+            _readSocket();
+          }
+          if (_filterStatus.readPlaintextNoLongerEmpty) {
+            _scheduleReadEvent();
+          }
+          if (_status == handshakeStatus) {
+            await _secureHandshake();
+          }
         }
       }
-      return _tryFilter();
     } catch (e, st) {
       _reportError(e, st);
     }
diff --git a/tools/VERSION b/tools/VERSION
index 0ee1a68..3e628b6 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 12
 PATCH 0
-PRERELEASE 245
+PRERELEASE 246
 PRERELEASE_PATCH 0
\ No newline at end of file