Version 2.12.0-66.0.dev

Merge commit 'ed2fc406896e5a345c2999d34db649e716957e53' into 'dev'
diff --git a/pkg/nnbd_migration/lib/src/edge_builder.dart b/pkg/nnbd_migration/lib/src/edge_builder.dart
index b76ee89..5fac352 100644
--- a/pkg/nnbd_migration/lib/src/edge_builder.dart
+++ b/pkg/nnbd_migration/lib/src/edge_builder.dart
@@ -1129,27 +1129,22 @@
     var type = node.type;
     _dispatch(type);
     var decoratedType = _variables.decoratedTypeAnnotation(source, type);
-    if (type is NamedType) {
-      // The main type of the is check historically could not be nullable.
-      // Making it nullable could change runtime behavior.
-      _graph.makeNonNullable(
-          decoratedType.node, IsCheckMainTypeOrigin(source, type));
-      _conditionInfo = _ConditionInfo(node,
-          isPure: expression is SimpleIdentifier,
-          postDominatingIntent:
-              _postDominatedLocals.isReferenceInScope(expression),
-          trueDemonstratesNonNullIntent: expressionNode);
-      if (node.notOperator != null) {
-        _conditionInfo = _conditionInfo.not(node);
-      }
-      if (!_assumeNonNullabilityInCasts) {
-        // TODO(mfairhurst): wire this to handleDowncast if we do not assume
-        // nullability.
-        assert(false);
-      }
-    } else if (type is GenericFunctionType) {
-      // TODO(brianwilkerson)
-      _unimplemented(node, 'Is expression with GenericFunctionType');
+    // The main type of the is check historically could not be nullable.
+    // Making it nullable could change runtime behavior.
+    _graph.makeNonNullable(
+        decoratedType.node, IsCheckMainTypeOrigin(source, type));
+    _conditionInfo = _ConditionInfo(node,
+        isPure: expression is SimpleIdentifier,
+        postDominatingIntent:
+            _postDominatedLocals.isReferenceInScope(expression),
+        trueDemonstratesNonNullIntent: expressionNode);
+    if (node.notOperator != null) {
+      _conditionInfo = _conditionInfo.not(node);
+    }
+    if (!_assumeNonNullabilityInCasts) {
+      // TODO(mfairhurst): wire this to handleDowncast if we do not assume
+      // nullability.
+      assert(false);
     }
     _flowAnalysis.isExpression_end(
         node, expression, node.notOperator != null, decoratedType);
diff --git a/pkg/nnbd_migration/test/api_test.dart b/pkg/nnbd_migration/test/api_test.dart
index d3075e1..34871b0 100644
--- a/pkg/nnbd_migration/test/api_test.dart
+++ b/pkg/nnbd_migration/test/api_test.dart
@@ -4066,6 +4066,24 @@
     await _checkSingleFileChanges(content, expected);
   }
 
+  Future<void> test_isExpression_with_function_type() async {
+    var content = '''
+void test(Function f) {
+  if (f is void Function()) {
+    f();
+  }
+}
+''';
+    var expected = '''
+void test(Function f) {
+  if (f is void Function()) {
+    f();
+  }
+}
+''';
+    await _checkSingleFileChanges(content, expected);
+  }
+
   Future<void> test_issue_40181() async {
     // This contrived example created an "exact nullable" type parameter bound
     // which propagated back to *all* instantiations of that parameter.
diff --git a/pkg/nnbd_migration/test/edge_builder_test.dart b/pkg/nnbd_migration/test/edge_builder_test.dart
index e8f81c6..1885167 100644
--- a/pkg/nnbd_migration/test/edge_builder_test.dart
+++ b/pkg/nnbd_migration/test/edge_builder_test.dart
@@ -4431,7 +4431,6 @@
         hard: false);
   }
 
-  @failingTest
   Future<void> test_isExpression_genericFunctionType() async {
     await analyze('''
 bool f(a) => a is int Function(String);
diff --git a/pkg/test_runner/lib/bot_results.dart b/pkg/test_runner/lib/bot_results.dart
index 88b2eb1..b5032f6 100644
--- a/pkg/test_runner/lib/bot_results.dart
+++ b/pkg/test_runner/lib/bot_results.dart
@@ -26,10 +26,8 @@
   final String outcome;
   final bool changed;
   final String commitHash;
-  // TODO(karlklose): this field is unnecessary with extended results and
-  // should be removed.
-  final bool flaked;
-  final bool isFlaky;
+  final bool flaked; // From optional flakiness_data argument to constructor.
+  final bool isFlaky; // From results.json after it is extended.
   final String previousOutcome;
 
   Result(
@@ -55,6 +53,7 @@
         isFlaky = map["flaky"] as bool,
         previousOutcome = map["previous_result"] as String,
         flaked = flakinessData != null &&
+            (flakinessData["active"] ?? true) == true &&
             (flakinessData["outcomes"] as List).contains(map["result"]);
 
   String get key => "$configuration:$name";
diff --git a/tools/VERSION b/tools/VERSION
index 073e310..e3c9cca 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 12
 PATCH 0
-PRERELEASE 65
+PRERELEASE 66
 PRERELEASE_PATCH 0
\ No newline at end of file
diff --git a/tools/bots/extend_results.dart b/tools/bots/extend_results.dart
index 225cc18..7dc7743 100644
--- a/tools/bots/extend_results.dart
+++ b/tools/bots/extend_results.dart
@@ -46,8 +46,9 @@
     result['commit_time'] = commitTime;
     result['build_number'] = buildNumber;
     result['builder_name'] = builderName;
-    result['flaky'] = (flaky != null);
-    result['previous_flaky'] = (priorFlaky != null);
+    result['flaky'] = flaky != null && (flaky['active'] ?? true) == true;
+    result['previous_flaky'] =
+        priorFlaky != null && (priorFlaky['active'] ?? true) == true;
     if (firstPriorResult != null) {
       result['previous_commit_hash'] = firstPriorResult['commit_hash'];
       result['previous_commit_time'] = firstPriorResult['commit_time'];
diff --git a/tools/bots/update_flakiness.dart b/tools/bots/update_flakiness.dart
index 337bf61..485fd38 100755
--- a/tools/bots/update_flakiness.dart
+++ b/tools/bots/update_flakiness.dart
@@ -41,6 +41,10 @@
       ? await loadResultsMap(options['input'])
       : <String, Map<String, dynamic>>{};
 
+  final resultsForInactiveFlakiness = {
+    for (final flakyTest in data.keys)
+      if (data[flakyTest]['active'] == false) flakyTest: <String>{}
+  };
   // Incrementally update the flakiness data with each observed result.
   for (final path in parameters) {
     final results = await loadResults(path);
@@ -49,6 +53,7 @@
       final String name = resultObject['name'] /*!*/;
       final String result = resultObject['result'] /*!*/;
       final key = '$configuration:$name';
+      resultsForInactiveFlakiness[key]?.add(result);
       Map<String, dynamic> newMap() => {};
       final testData = data.putIfAbsent(key, newMap);
       testData['configuration'] = configuration;
@@ -96,10 +101,20 @@
   for (final key in keys) {
     final testData = data[key];
     if (testData['outcomes'].length < 2) continue;
-    // Forgive tests that have been stable for 100 builds.
-    if (!options['no-forgive'] && testData['current_counter'] >= 100) {
-      continue;
+    // Reactivate inactive flaky results that are flaky again.
+    if (testData['active'] == false) {
+      if (resultsForInactiveFlakiness[key].length > 1) {
+        testData['active'] == true;
+        testData['reactivation_count'] =
+            (testData['reactivation_count'] ?? 0) + 1;
+      }
+    } else if (!options['no-forgive'] && testData['current_counter'] >= 100) {
+      // Forgive tests that have been stable for 100 builds.
+      testData['active'] = false;
+    } else {
+      testData['active'] = true;
     }
+
     sink.writeln(jsonEncode(testData));
   }
 }