Add summary support for `if` elements.

Change-Id: I32582b9608bcf92cabfac94e1d796036198818a5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/97023
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/analyzer/lib/src/generated/testing/ast_test_factory.dart b/pkg/analyzer/lib/src/generated/testing/ast_test_factory.dart
index 2cd3ae4..a2e6994 100644
--- a/pkg/analyzer/lib/src/generated/testing/ast_test_factory.dart
+++ b/pkg/analyzer/lib/src/generated/testing/ast_test_factory.dart
@@ -670,6 +670,20 @@
         .toList();
   }
 
+  static IfElement ifElement(
+          Expression condition, CollectionElement thenElement,
+          [CollectionElement elseElement]) =>
+      astFactory.ifElement(
+          ifKeyword: TokenFactory.tokenFromKeyword(Keyword.IF),
+          leftParenthesis: TokenFactory.tokenFromType(TokenType.OPEN_PAREN),
+          condition: condition,
+          rightParenthesis: TokenFactory.tokenFromType(TokenType.CLOSE_PAREN),
+          thenElement: thenElement,
+          elseKeyword: elseElement == null
+              ? null
+              : TokenFactory.tokenFromKeyword(Keyword.ELSE),
+          elseElement: elseElement);
+
   static IfStatement ifStatement(
           Expression condition, Statement thenStatement) =>
       ifStatement2(condition, thenStatement, null);
diff --git a/pkg/analyzer/lib/src/summary/expr_builder.dart b/pkg/analyzer/lib/src/summary/expr_builder.dart
index 629798e..960e342 100644
--- a/pkg/analyzer/lib/src/summary/expr_builder.dart
+++ b/pkg/analyzer/lib/src/summary/expr_builder.dart
@@ -323,6 +323,12 @@
         case UnlinkedExprOperation.nullAwareSpreadElement:
           _pushSpread(TokenType.PERIOD_PERIOD_PERIOD_QUESTION);
           break;
+        case UnlinkedExprOperation.ifElement:
+          _pushIfElement(false);
+          break;
+        case UnlinkedExprOperation.ifElseElement:
+          _pushIfElement(true);
+          break;
         case UnlinkedExprOperation.cascadeSectionBegin:
         case UnlinkedExprOperation.cascadeSectionEnd:
         case UnlinkedExprOperation.pushLocalFunctionReference:
@@ -650,6 +656,13 @@
     _push(AstTestFactory.propertyAccess(target, propertyNode));
   }
 
+  void _pushIfElement(bool hasElse) {
+    CollectionElement elseElement = hasElse ? _popCollectionElement() : null;
+    CollectionElement thenElement = _popCollectionElement();
+    Expression condition = _pop();
+    _push(AstTestFactory.ifElement(condition, thenElement, elseElement));
+  }
+
   void _pushInstanceCreation() {
     EntityRef ref = _uc.references[refPtr++];
     ReferenceInfo info = resynthesizer.getReferenceInfo(ref.reference);
diff --git a/pkg/analyzer/lib/src/summary/format.fbs b/pkg/analyzer/lib/src/summary/format.fbs
index 6745d48..6cd172f 100644
--- a/pkg/analyzer/lib/src/summary/format.fbs
+++ b/pkg/analyzer/lib/src/summary/format.fbs
@@ -980,7 +980,18 @@
 
   /// Pop the top value from the stack, convert it to a spread element of type
   /// `...?`, and push the result back onto the stack.
-  nullAwareSpreadElement
+  nullAwareSpreadElement,
+
+  /// Pop the top two values from the stack.  The first is a condition
+  /// and the second is a collection element.  Push an "if" element having the
+  /// given condition, with the collection element as its "then" clause.
+  ifElement,
+
+  /// Pop the top three values from the stack.  The first is a condition and the
+  /// other two are collection elements.  Push an "if" element having the given
+  /// condition, with the two collection elements as its "then" and "else"
+  /// clauses, respectively.
+  ifElseElement
 }
 
 /// Enum used to indicate the kind of a parameter.
diff --git a/pkg/analyzer/lib/src/summary/idl.dart b/pkg/analyzer/lib/src/summary/idl.dart
index f000abb..c9e84f8 100644
--- a/pkg/analyzer/lib/src/summary/idl.dart
+++ b/pkg/analyzer/lib/src/summary/idl.dart
@@ -3876,6 +3876,17 @@
   /// Pop the top value from the stack, convert it to a spread element of type
   /// `...?`, and push the result back onto the stack.
   nullAwareSpreadElement,
+
+  /// Pop the top two values from the stack.  The first is a condition
+  /// and the second is a collection element.  Push an "if" element having the
+  /// given condition, with the collection element as its "then" clause.
+  ifElement,
+
+  /// Pop the top three values from the stack.  The first is a condition and the
+  /// other two are collection elements.  Push an "if" element having the given
+  /// condition, with the two collection elements as its "then" and "else"
+  /// clauses, respectively.
+  ifElseElement,
 }
 
 /// Unlinked summary information about an import declaration.
diff --git a/pkg/analyzer/lib/src/summary/summarize_const_expr.dart b/pkg/analyzer/lib/src/summary/summarize_const_expr.dart
index 71af96b..f367937 100644
--- a/pkg/analyzer/lib/src/summary/summarize_const_expr.dart
+++ b/pkg/analyzer/lib/src/summary/summarize_const_expr.dart
@@ -548,6 +548,16 @@
       operations.add(isNullAware
           ? UnlinkedExprOperation.nullAwareSpreadElement
           : UnlinkedExprOperation.spreadElement);
+    } else if (element is IfElement) {
+      _serialize(element.condition);
+      _serializeCollectionElement(element.thenElement);
+      var elseElement = element.elseElement;
+      if (elseElement == null) {
+        operations.add(UnlinkedExprOperation.ifElement);
+      } else {
+        _serializeCollectionElement(elseElement);
+        operations.add(UnlinkedExprOperation.ifElseElement);
+      }
     } else {
       // TODO(paulberry): Implement serialization for spread and control flow
       //  elements.
diff --git a/pkg/analyzer/test/src/summary/element_text.dart b/pkg/analyzer/test/src/summary/element_text.dart
index 6813ec5..b3a61b2 100644
--- a/pkg/analyzer/test/src/summary/element_text.dart
+++ b/pkg/analyzer/test/src/summary/element_text.dart
@@ -673,6 +673,16 @@
     } else if (e is SpreadElement) {
       buffer.write(e.spreadOperator.lexeme);
       writeNode(e.expression);
+    } else if (e is IfElement) {
+      buffer.write('if (');
+      writeNode(e.condition);
+      buffer.write(') ');
+      writeNode(e.thenElement);
+      var elseElement = e.elseElement;
+      if (elseElement != null) {
+        buffer.write(' else ');
+        writeNode(elseElement);
+      }
     } else {
       fail('Unsupported expression type: ${e.runtimeType}');
     }
diff --git a/pkg/analyzer/test/src/summary/resynthesize_common.dart b/pkg/analyzer/test/src/summary/resynthesize_common.dart
index d90e62a..bb73374 100644
--- a/pkg/analyzer/test/src/summary/resynthesize_common.dart
+++ b/pkg/analyzer/test/src/summary/resynthesize_common.dart
@@ -2556,6 +2556,34 @@
 ''');
   }
 
+  test_const_list_if() async {
+    experimentStatus = ExperimentStatus(control_flow_collections: true);
+    var library = await checkLibrary('''
+const Object x = const <int>[if (true) 1];
+''');
+    checkElementText(
+        library,
+        '''
+const Object x = const <
+        int/*location: dart:core;int*/>[if (true) 1];
+''',
+        withTypes: true);
+  }
+
+  test_const_list_if_else() async {
+    experimentStatus = ExperimentStatus(control_flow_collections: true);
+    var library = await checkLibrary('''
+const Object x = const <int>[if (true) 1 else 2];
+''');
+    checkElementText(
+        library,
+        '''
+const Object x = const <
+        int/*location: dart:core;int*/>[if (true) 1 else 2];
+''',
+        withTypes: true);
+  }
+
   test_const_list_inferredType() async {
     // The summary needs to contain enough information so that when the constant
     // is resynthesized, the constant value can get the type that was computed
@@ -2624,6 +2652,36 @@
     }
   }
 
+  test_const_map_if() async {
+    experimentStatus = ExperimentStatus(control_flow_collections: true);
+    var library = await checkLibrary('''
+const Object x = const <int, int>{if (true) 1: 2};
+''');
+    checkElementText(
+        library,
+        '''
+const Object x = const <
+        int/*location: dart:core;int*/,
+        int/*location: dart:core;int*/>{if (true) 1: 2}/*isMap*/;
+''',
+        withTypes: true);
+  }
+
+  test_const_map_if_else() async {
+    experimentStatus = ExperimentStatus(control_flow_collections: true);
+    var library = await checkLibrary('''
+const Object x = const <int, int>{if (true) 1: 2 else 3: 4];
+''');
+    checkElementText(
+        library,
+        '''
+const Object x = const <
+        int/*location: dart:core;int*/,
+        int/*location: dart:core;int*/>{if (true) 1: 2 else 3: 4}/*isMap*/;
+''',
+        withTypes: true);
+  }
+
   test_const_map_inferredType() async {
     // The summary needs to contain enough information so that when the constant
     // is resynthesized, the constant value can get the type that was computed
@@ -3141,6 +3199,34 @@
 ''');
   }
 
+  test_const_set_if() async {
+    experimentStatus = ExperimentStatus(control_flow_collections: true);
+    var library = await checkLibrary('''
+const Object x = const <int>{if (true) 1};
+''');
+    checkElementText(
+        library,
+        '''
+const Object x = const <
+        int/*location: dart:core;int*/>{if (true) 1}/*isSet*/;
+''',
+        withTypes: true);
+  }
+
+  test_const_set_if_else() async {
+    experimentStatus = ExperimentStatus(control_flow_collections: true);
+    var library = await checkLibrary('''
+const Object x = const <int>{if (true) 1 else 2];
+''');
+    checkElementText(
+        library,
+        '''
+const Object x = const <
+        int/*location: dart:core;int*/>{if (true) 1 else 2}/*isSet*/;
+''',
+        withTypes: true);
+  }
+
   test_const_set_inferredType() async {
     // The summary needs to contain enough information so that when the constant
     // is resynthesized, the constant value can get the type that was computed
diff --git a/pkg/analyzer/test/src/summary/summary_common.dart b/pkg/analyzer/test/src/summary/summary_common.dart
index c69533d..e5a4dfb 100644
--- a/pkg/analyzer/test/src/summary/summary_common.dart
+++ b/pkg/analyzer/test/src/summary/summary_common.dart
@@ -2754,6 +2754,44 @@
         ]);
   }
 
+  test_constExpr_list_if() {
+    experimentStatus = ExperimentStatus(
+        control_flow_collections: true, spread_collections: true);
+    UnlinkedVariable variable =
+        serializeVariableText('const v = [if (true) 1];');
+    assertUnlinkedConst(variable.initializer.bodyExpr, '[if (true) 1]',
+        operators: [
+          UnlinkedExprOperation.pushTrue,
+          UnlinkedExprOperation.pushInt,
+          UnlinkedExprOperation.ifElement,
+          UnlinkedExprOperation.makeUntypedList
+        ],
+        ints: [
+          1,
+          1
+        ]);
+  }
+
+  test_constExpr_list_if_else() {
+    experimentStatus = ExperimentStatus(
+        control_flow_collections: true, spread_collections: true);
+    UnlinkedVariable variable =
+        serializeVariableText('const v = [if (true) 1 else 2];');
+    assertUnlinkedConst(variable.initializer.bodyExpr, '[if (true) 1 else 2]',
+        operators: [
+          UnlinkedExprOperation.pushTrue,
+          UnlinkedExprOperation.pushInt,
+          UnlinkedExprOperation.pushInt,
+          UnlinkedExprOperation.ifElseElement,
+          UnlinkedExprOperation.makeUntypedList
+        ],
+        ints: [
+          1,
+          2,
+          1
+        ]);
+  }
+
   test_constExpr_list_spread() {
     experimentStatus = ExperimentStatus(
         control_flow_collections: true, spread_collections: true);
@@ -3151,6 +3189,67 @@
     );
   }
 
+  test_constExpr_map_if() {
+    experimentStatus = ExperimentStatus(
+        control_flow_collections: true, spread_collections: true);
+    UnlinkedVariable variable =
+        serializeVariableText('const v = <int, int>{if (true) 1 : 2};');
+    assertUnlinkedConst(
+        variable.initializer.bodyExpr, '<int, int>{if (true) 1 : 2}',
+        operators: [
+          UnlinkedExprOperation.pushTrue,
+          UnlinkedExprOperation.pushInt,
+          UnlinkedExprOperation.pushInt,
+          UnlinkedExprOperation.makeMapLiteralEntry,
+          UnlinkedExprOperation.ifElement,
+          UnlinkedExprOperation.makeTypedMap2
+        ],
+        ints: [
+          1,
+          2,
+          1
+        ],
+        referenceValidators: [
+          (EntityRef r) => checkTypeRef(r, 'dart:core', 'int',
+              expectedKind: ReferenceKind.classOrEnum),
+          (EntityRef r) => checkTypeRef(r, 'dart:core', 'int',
+              expectedKind: ReferenceKind.classOrEnum)
+        ]);
+  }
+
+  test_constExpr_map_if_else() {
+    experimentStatus = ExperimentStatus(
+        control_flow_collections: true, spread_collections: true);
+    UnlinkedVariable variable = serializeVariableText(
+        'const v = <int, int>{if (true) 1 : 2 else 3 : 4};');
+    assertUnlinkedConst(
+        variable.initializer.bodyExpr, '<int, int>{if (true) 1 : 2 else 3 : 4}',
+        operators: [
+          UnlinkedExprOperation.pushTrue,
+          UnlinkedExprOperation.pushInt,
+          UnlinkedExprOperation.pushInt,
+          UnlinkedExprOperation.makeMapLiteralEntry,
+          UnlinkedExprOperation.pushInt,
+          UnlinkedExprOperation.pushInt,
+          UnlinkedExprOperation.makeMapLiteralEntry,
+          UnlinkedExprOperation.ifElseElement,
+          UnlinkedExprOperation.makeTypedMap2
+        ],
+        ints: [
+          1,
+          2,
+          3,
+          4,
+          1
+        ],
+        referenceValidators: [
+          (EntityRef r) => checkTypeRef(r, 'dart:core', 'int',
+              expectedKind: ReferenceKind.classOrEnum),
+          (EntityRef r) => checkTypeRef(r, 'dart:core', 'int',
+              expectedKind: ReferenceKind.classOrEnum)
+        ]);
+  }
+
   test_constExpr_map_spread() {
     experimentStatus = ExperimentStatus(
         control_flow_collections: true, spread_collections: true);
@@ -3925,6 +4024,53 @@
         operators: [UnlinkedExprOperation.pushTrue]);
   }
 
+  test_constExpr_set_if() {
+    experimentStatus = ExperimentStatus(
+        control_flow_collections: true, spread_collections: true);
+    UnlinkedVariable variable =
+        serializeVariableText('const v = <int>{if (true) 1};');
+    assertUnlinkedConst(variable.initializer.bodyExpr, '<int>{if (true) 1}',
+        operators: [
+          UnlinkedExprOperation.pushTrue,
+          UnlinkedExprOperation.pushInt,
+          UnlinkedExprOperation.ifElement,
+          UnlinkedExprOperation.makeTypedSet
+        ],
+        ints: [
+          1,
+          1
+        ],
+        referenceValidators: [
+          (EntityRef r) => checkTypeRef(r, 'dart:core', 'int',
+              expectedKind: ReferenceKind.classOrEnum)
+        ]);
+  }
+
+  test_constExpr_set_if_else() {
+    experimentStatus = ExperimentStatus(
+        control_flow_collections: true, spread_collections: true);
+    UnlinkedVariable variable =
+        serializeVariableText('const v = <int>{if (true) 1 else 2};');
+    assertUnlinkedConst(
+        variable.initializer.bodyExpr, '<int>{if (true) 1 else 2}',
+        operators: [
+          UnlinkedExprOperation.pushTrue,
+          UnlinkedExprOperation.pushInt,
+          UnlinkedExprOperation.pushInt,
+          UnlinkedExprOperation.ifElseElement,
+          UnlinkedExprOperation.makeTypedSet
+        ],
+        ints: [
+          1,
+          2,
+          1
+        ],
+        referenceValidators: [
+          (EntityRef r) => checkTypeRef(r, 'dart:core', 'int',
+              expectedKind: ReferenceKind.classOrEnum)
+        ]);
+  }
+
   test_constExpr_set_spread() {
     experimentStatus = ExperimentStatus(
         control_flow_collections: true, spread_collections: true);