[kernel] Collection concatenation nodes.

These arise when the constant evaluator partially evaluates collections
containing spreads or control-flow constructs with unevaluated
subexpressions. They are removed by the final constant evaluation.

Change-Id: Icdd155c4805cbcefe6aa4b45c2f85ec258e7bd36
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/95760
Commit-Queue: Aske Simon Christensen <askesc@google.com>
Reviewed-by: Dmitry Stefantsov <dmitryas@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index 6c5ccc6..60b576f 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -4775,6 +4775,24 @@
   }
 
   @override
+  visitListConcatenation(ListConcatenation node) {
+    // Only occurs inside unevaluated constants.
+    throw new UnsupportedError("List concatenation");
+  }
+
+  @override
+  visitSetConcatenation(SetConcatenation node) {
+    // Only occurs inside unevaluated constants.
+    throw new UnsupportedError("Set concatenation");
+  }
+
+  @override
+  visitMapConcatenation(MapConcatenation node) {
+    // Only occurs inside unevaluated constants.
+    throw new UnsupportedError("Map concatenation");
+  }
+
+  @override
   visitIsExpression(IsExpression node) {
     return _emitIsExpression(node.operand, node.type);
   }
diff --git a/pkg/kernel/binary.md b/pkg/kernel/binary.md
index 5fab418..613741e7 100644
--- a/pkg/kernel/binary.md
+++ b/pkg/kernel/binary.md
@@ -137,7 +137,7 @@
 
 type ComponentFile {
   UInt32 magic = 0x90ABCDEF;
-  UInt32 formatVersion = 20;
+  UInt32 formatVersion = 21;
   List<String> problemsAsJson; // Described in problems.md.
   Library[] libraries;
   UriSource sourceMap;
@@ -696,6 +696,28 @@
   List<Expression> expressions;
 }
 
+type ListConcatenation extends Expression {
+  Byte tag = 111;
+  FileOffset fileOffset;
+  DartType typeArgument;
+  List<Expression> lists;
+}
+
+type SetConcatenation extends Expression {
+  Byte tag = 112;
+  FileOffset fileOffset;
+  DartType typeArgument;
+  List<Expression> sets;
+}
+
+type MapConcatenation extends Expression {
+  Byte tag = 113;
+  FileOffset fileOffset;
+  DartType keyType;
+  DartType valueType;
+  List<Expression> maps;
+}
+
 type IsExpression extends Expression {
   Byte tag = 37;
   FileOffset fileOffset;
diff --git a/pkg/kernel/lib/ast.dart b/pkg/kernel/lib/ast.dart
index 0f9cfa4..2d7e3a1 100644
--- a/pkg/kernel/lib/ast.dart
+++ b/pkg/kernel/lib/ast.dart
@@ -3204,6 +3204,116 @@
   }
 }
 
+/// Concatenate lists into a single list.
+///
+/// If [lists] is empty then an empty list is returned.
+///
+/// These arise from spread and control-flow elements in const list literals
+/// containing unevaluated subexpressions. They only ever occur within
+/// unevaluated constants in constant expressions.
+class ListConcatenation extends Expression {
+  DartType typeArgument;
+  final List<Expression> lists;
+
+  ListConcatenation(this.lists, {this.typeArgument: const DynamicType()}) {
+    setParents(lists, this);
+  }
+
+  DartType getStaticType(TypeEnvironment types) {
+    return types.literalListType(typeArgument);
+  }
+
+  accept(ExpressionVisitor v) => v.visitListConcatenation(this);
+  accept1(ExpressionVisitor1 v, arg) => v.visitListConcatenation(this, arg);
+
+  visitChildren(Visitor v) {
+    typeArgument?.accept(v);
+    visitList(lists, v);
+  }
+
+  transformChildren(Transformer v) {
+    typeArgument = v.visitDartType(typeArgument);
+    transformList(lists, v, this);
+  }
+}
+
+/// Concatenate sets into a single set.
+///
+/// If [sets] is empty then an empty set is returned.
+///
+/// These arise from spread and control-flow elements in const set literals
+/// containing unevaluated subexpressions. They only ever occur within
+/// unevaluated constants in constant expressions.
+///
+/// Duplicated values in or across the sets will result in a compile-time error
+/// during constant evaluation.
+class SetConcatenation extends Expression {
+  DartType typeArgument;
+  final List<Expression> sets;
+
+  SetConcatenation(this.sets, {this.typeArgument: const DynamicType()}) {
+    setParents(sets, this);
+  }
+
+  DartType getStaticType(TypeEnvironment types) {
+    return types.literalSetType(typeArgument);
+  }
+
+  accept(ExpressionVisitor v) => v.visitSetConcatenation(this);
+  accept1(ExpressionVisitor1 v, arg) => v.visitSetConcatenation(this, arg);
+
+  visitChildren(Visitor v) {
+    typeArgument?.accept(v);
+    visitList(sets, v);
+  }
+
+  transformChildren(Transformer v) {
+    typeArgument = v.visitDartType(typeArgument);
+    transformList(sets, v, this);
+  }
+}
+
+/// Concatenate maps into a single map.
+///
+/// If [maps] is empty then an empty map is returned.
+///
+/// These arise from spread and control-flow elements in const map literals
+/// containing unevaluated subexpressions. They only ever occur within
+/// unevaluated constants in constant expressions.
+///
+/// Duplicated keys in or across the maps will result in a compile-time error
+/// during constant evaluation.
+class MapConcatenation extends Expression {
+  DartType keyType;
+  DartType valueType;
+  final List<Expression> maps;
+
+  MapConcatenation(this.maps,
+      {this.keyType: const DynamicType(),
+      this.valueType: const DynamicType()}) {
+    setParents(maps, this);
+  }
+
+  DartType getStaticType(TypeEnvironment types) {
+    return types.literalMapType(keyType, valueType);
+  }
+
+  accept(ExpressionVisitor v) => v.visitMapConcatenation(this);
+  accept1(ExpressionVisitor1 v, arg) => v.visitMapConcatenation(this, arg);
+
+  visitChildren(Visitor v) {
+    keyType?.accept(v);
+    valueType?.accept(v);
+    visitList(maps, v);
+  }
+
+  transformChildren(Transformer v) {
+    keyType = v.visitDartType(keyType);
+    valueType = v.visitDartType(valueType);
+    transformList(maps, v, this);
+  }
+}
+
 /// Expression of form `x is T`.
 class IsExpression extends Expression {
   Expression operand;
diff --git a/pkg/kernel/lib/binary/ast_from_binary.dart b/pkg/kernel/lib/binary/ast_from_binary.dart
index 29fc2cc..f5b7c33 100644
--- a/pkg/kernel/lib/binary/ast_from_binary.dart
+++ b/pkg/kernel/lib/binary/ast_from_binary.dart
@@ -1511,6 +1511,25 @@
         int offset = readOffset();
         return new StringConcatenation(readExpressionList())
           ..fileOffset = offset;
+      case Tag.ListConcatenation:
+        int offset = readOffset();
+        var typeArgument = readDartType();
+        return new ListConcatenation(readExpressionList(),
+            typeArgument: typeArgument)
+          ..fileOffset = offset;
+      case Tag.SetConcatenation:
+        int offset = readOffset();
+        var typeArgument = readDartType();
+        return new SetConcatenation(readExpressionList(),
+            typeArgument: typeArgument)
+          ..fileOffset = offset;
+      case Tag.MapConcatenation:
+        int offset = readOffset();
+        var keyType = readDartType();
+        var valueType = readDartType();
+        return new MapConcatenation(readExpressionList(),
+            keyType: keyType, valueType: valueType)
+          ..fileOffset = offset;
       case Tag.IsExpression:
         int offset = readOffset();
         return new IsExpression(readExpression(), readDartType())
diff --git a/pkg/kernel/lib/binary/ast_to_binary.dart b/pkg/kernel/lib/binary/ast_to_binary.dart
index 383e6ba..db91bfb 100644
--- a/pkg/kernel/lib/binary/ast_to_binary.dart
+++ b/pkg/kernel/lib/binary/ast_to_binary.dart
@@ -1466,6 +1466,31 @@
   }
 
   @override
+  void visitListConcatenation(ListConcatenation node) {
+    writeByte(Tag.ListConcatenation);
+    writeOffset(node.fileOffset);
+    writeNode(node.typeArgument);
+    writeNodeList(node.lists);
+  }
+
+  @override
+  void visitSetConcatenation(SetConcatenation node) {
+    writeByte(Tag.SetConcatenation);
+    writeOffset(node.fileOffset);
+    writeNode(node.typeArgument);
+    writeNodeList(node.sets);
+  }
+
+  @override
+  void visitMapConcatenation(MapConcatenation node) {
+    writeByte(Tag.MapConcatenation);
+    writeOffset(node.fileOffset);
+    writeNode(node.keyType);
+    writeNode(node.valueType);
+    writeNodeList(node.maps);
+  }
+
+  @override
   void visitIsExpression(IsExpression node) {
     writeByte(Tag.IsExpression);
     writeOffset(node.fileOffset);
diff --git a/pkg/kernel/lib/binary/tag.dart b/pkg/kernel/lib/binary/tag.dart
index ff6e228..ba6cc4e 100644
--- a/pkg/kernel/lib/binary/tag.dart
+++ b/pkg/kernel/lib/binary/tag.dart
@@ -50,6 +50,9 @@
   static const int LogicalExpression = 34;
   static const int ConditionalExpression = 35;
   static const int StringConcatenation = 36;
+  static const int ListConcatenation = 111;
+  static const int SetConcatenation = 112;
+  static const int MapConcatenation = 113;
   static const int IsExpression = 37;
   static const int AsExpression = 38;
   static const int StringLiteral = 39;
@@ -123,6 +126,9 @@
   /// 108 is occupied by [RedirectingFactoryConstructor] (member).
   /// 109 is occupied by [SetLiteral] (expression).
   /// 110 is occupied by [ConstSetLiteral] (expression).
+  /// 111 is occupied by [ListConcatenation] (expression).
+  /// 112 is occupied by [SetConcatenation] (expression).
+  /// 113 is occupied by [MapConcatenation] (expression).
 
   static const int SpecializedTagHighBit = 0x80; // 10000000
   static const int SpecializedTagMask = 0xF8; // 11111000
@@ -139,7 +145,7 @@
   /// Internal version of kernel binary format.
   /// Bump it when making incompatible changes in kernel binaries.
   /// Keep in sync with runtime/vm/kernel_binary.h, pkg/kernel/binary.md.
-  static const int BinaryFormatVersion = 20;
+  static const int BinaryFormatVersion = 21;
 }
 
 abstract class ConstantTag {
diff --git a/pkg/kernel/lib/clone.dart b/pkg/kernel/lib/clone.dart
index a059436..4105a43 100644
--- a/pkg/kernel/lib/clone.dart
+++ b/pkg/kernel/lib/clone.dart
@@ -190,6 +190,21 @@
     return new StringConcatenation(node.expressions.map(clone).toList());
   }
 
+  visitListConcatenation(ListConcatenation node) {
+    return new ListConcatenation(node.lists.map(clone).toList(),
+        typeArgument: visitType(node.typeArgument));
+  }
+
+  visitSetConcatenation(SetConcatenation node) {
+    return new SetConcatenation(node.sets.map(clone).toList(),
+        typeArgument: visitType(node.typeArgument));
+  }
+
+  visitMapConcatenation(MapConcatenation node) {
+    return new MapConcatenation(node.maps.map(clone).toList(),
+        keyType: visitType(node.keyType), valueType: visitType(node.valueType));
+  }
+
   visitIsExpression(IsExpression node) {
     return new IsExpression(clone(node.operand), visitType(node.type));
   }
diff --git a/pkg/kernel/lib/text/ast_to_text.dart b/pkg/kernel/lib/text/ast_to_text.dart
index 94d2f55..5e7d228 100644
--- a/pkg/kernel/lib/text/ast_to_text.dart
+++ b/pkg/kernel/lib/text/ast_to_text.dart
@@ -1272,6 +1272,33 @@
     state = WORD;
   }
 
+  visitListConcatenation(ListConcatenation node) {
+    bool first = true;
+    for (Expression part in node.lists) {
+      if (!first) writeSpaced('+');
+      writeExpression(part);
+      first = false;
+    }
+  }
+
+  visitSetConcatenation(SetConcatenation node) {
+    bool first = true;
+    for (Expression part in node.sets) {
+      if (!first) writeSpaced('+');
+      writeExpression(part);
+      first = false;
+    }
+  }
+
+  visitMapConcatenation(MapConcatenation node) {
+    bool first = true;
+    for (Expression part in node.maps) {
+      if (!first) writeSpaced('+');
+      writeExpression(part);
+      first = false;
+    }
+  }
+
   visitIsExpression(IsExpression node) {
     writeExpression(node.operand, Precedence.BITWISE_OR);
     writeSpaced('is');
diff --git a/pkg/kernel/lib/text/text_serialization_verifier.dart b/pkg/kernel/lib/text/text_serialization_verifier.dart
index 2625376..b9f2662 100644
--- a/pkg/kernel/lib/text/text_serialization_verifier.dart
+++ b/pkg/kernel/lib/text/text_serialization_verifier.dart
@@ -888,6 +888,24 @@
   }
 
   @override
+  void visitListConcatenation(ListConcatenation node) {
+    storeLastSeenUriAndOffset(node);
+    makeExpressionRoundTrip(node);
+  }
+
+  @override
+  void visitSetConcatenation(SetConcatenation node) {
+    storeLastSeenUriAndOffset(node);
+    makeExpressionRoundTrip(node);
+  }
+
+  @override
+  void visitMapConcatenation(MapConcatenation node) {
+    storeLastSeenUriAndOffset(node);
+    makeExpressionRoundTrip(node);
+  }
+
+  @override
   void visitConditionalExpression(ConditionalExpression node) {
     storeLastSeenUriAndOffset(node);
     makeExpressionRoundTrip(node);
diff --git a/pkg/kernel/lib/type_checker.dart b/pkg/kernel/lib/type_checker.dart
index 56eb744..7f04bce 100644
--- a/pkg/kernel/lib/type_checker.dart
+++ b/pkg/kernel/lib/type_checker.dart
@@ -683,6 +683,36 @@
   }
 
   @override
+  DartType visitListConcatenation(ListConcatenation node) {
+    DartType type = environment.literalListType(node.typeArgument);
+    for (Expression part in node.lists) {
+      DartType partType = visitExpression(part);
+      checkAssignable(node, type, partType);
+    }
+    return type;
+  }
+
+  @override
+  DartType visitSetConcatenation(SetConcatenation node) {
+    DartType type = environment.literalSetType(node.typeArgument);
+    for (Expression part in node.sets) {
+      DartType partType = visitExpression(part);
+      checkAssignable(node, type, partType);
+    }
+    return type;
+  }
+
+  @override
+  DartType visitMapConcatenation(MapConcatenation node) {
+    DartType type = environment.literalMapType(node.keyType, node.valueType);
+    for (Expression part in node.maps) {
+      DartType partType = visitExpression(part);
+      checkAssignable(node, type, partType);
+    }
+    return type;
+  }
+
+  @override
   DartType visitStringLiteral(StringLiteral node) {
     return environment.stringType;
   }
diff --git a/pkg/kernel/lib/visitor.dart b/pkg/kernel/lib/visitor.dart
index 53ea440..0cecee1 100644
--- a/pkg/kernel/lib/visitor.dart
+++ b/pkg/kernel/lib/visitor.dart
@@ -38,6 +38,9 @@
       defaultExpression(node);
   R visitStringConcatenation(StringConcatenation node) =>
       defaultExpression(node);
+  R visitListConcatenation(ListConcatenation node) => defaultExpression(node);
+  R visitSetConcatenation(SetConcatenation node) => defaultExpression(node);
+  R visitMapConcatenation(MapConcatenation node) => defaultExpression(node);
   R visitIsExpression(IsExpression node) => defaultExpression(node);
   R visitAsExpression(AsExpression node) => defaultExpression(node);
   R visitSymbolLiteral(SymbolLiteral node) => defaultExpression(node);
@@ -161,6 +164,9 @@
       defaultExpression(node);
   R visitStringConcatenation(StringConcatenation node) =>
       defaultExpression(node);
+  R visitListConcatenation(ListConcatenation node) => defaultExpression(node);
+  R visitSetConcatenation(SetConcatenation node) => defaultExpression(node);
+  R visitMapConcatenation(MapConcatenation node) => defaultExpression(node);
   R visitIsExpression(IsExpression node) => defaultExpression(node);
   R visitAsExpression(AsExpression node) => defaultExpression(node);
   R visitSymbolLiteral(SymbolLiteral node) => defaultExpression(node);
@@ -496,6 +502,12 @@
       defaultExpression(node, arg);
   R visitStringConcatenation(StringConcatenation node, T arg) =>
       defaultExpression(node, arg);
+  R visitListConcatenation(ListConcatenation node, T arg) =>
+      defaultExpression(node, arg);
+  R visitSetConcatenation(SetConcatenation node, T arg) =>
+      defaultExpression(node, arg);
+  R visitMapConcatenation(MapConcatenation node, T arg) =>
+      defaultExpression(node, arg);
   R visitIsExpression(IsExpression node, T arg) => defaultExpression(node, arg);
   R visitAsExpression(AsExpression node, T arg) => defaultExpression(node, arg);
   R visitSymbolLiteral(SymbolLiteral node, T arg) =>
diff --git a/runtime/vm/compiler/frontend/constant_evaluator.cc b/runtime/vm/compiler/frontend/constant_evaluator.cc
index 6733249..44f3bbd 100644
--- a/runtime/vm/compiler/frontend/constant_evaluator.cc
+++ b/runtime/vm/compiler/frontend/constant_evaluator.cc
@@ -94,6 +94,14 @@
       case kStringConcatenation:
         EvaluateStringConcatenation();
         break;
+      case kListConcatenation:
+      case kSetConcatenation:
+      case kMapConcatenation:
+        // These only occur inside unevaluated constants, so if we decide to
+        // remove support for late evaluation of environment constants from
+        // dill files in the VM, an implementation here will not be necessary.
+        UNIMPLEMENTED();
+        break;
       case kSymbolLiteral:
         EvaluateSymbolLiteral();
         break;
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
index 7e77d3d..7a3404e 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
@@ -1299,6 +1299,13 @@
       return BuildConditionalExpression(position);
     case kStringConcatenation:
       return BuildStringConcatenation(position);
+    case kListConcatenation:
+    case kSetConcatenation:
+    case kMapConcatenation:
+      // Collection concatenation operations are removed by the constant
+      // evaluator.
+      UNREACHABLE();
+      break;
     case kIsExpression:
       return BuildIsExpression(position);
     case kAsExpression:
diff --git a/runtime/vm/compiler/frontend/kernel_fingerprints.cc b/runtime/vm/compiler/frontend/kernel_fingerprints.cc
index 58675d6..c9cdd92 100644
--- a/runtime/vm/compiler/frontend/kernel_fingerprints.cc
+++ b/runtime/vm/compiler/frontend/kernel_fingerprints.cc
@@ -447,6 +447,13 @@
       ReadPosition();                           // read position.
       CalculateListOfExpressionsFingerprint();  // read list of expressions.
       return;
+    case kListConcatenation:
+    case kSetConcatenation:
+    case kMapConcatenation:
+      // Collection concatenation operations are removed by the constant
+      // evaluator.
+      UNREACHABLE();
+      break;
     case kIsExpression:
       ReadPosition();                    // read position.
       CalculateExpressionFingerprint();  // read operand.
diff --git a/runtime/vm/compiler/frontend/kernel_translation_helper.cc b/runtime/vm/compiler/frontend/kernel_translation_helper.cc
index 6194c70..d0b79b9 100644
--- a/runtime/vm/compiler/frontend/kernel_translation_helper.cc
+++ b/runtime/vm/compiler/frontend/kernel_translation_helper.cc
@@ -2197,6 +2197,13 @@
       ReadPosition();           // read position.
       SkipListOfExpressions();  // read list of expressions.
       return;
+    case kListConcatenation:
+    case kSetConcatenation:
+    case kMapConcatenation:
+      // Collection concatenation operations are removed by the constant
+      // evaluator.
+      UNREACHABLE();
+      break;
     case kIsExpression:
       ReadPosition();    // read position.
       SkipExpression();  // read operand.
diff --git a/runtime/vm/compiler/frontend/scope_builder.cc b/runtime/vm/compiler/frontend/scope_builder.cc
index 71c2e61..7ab81b7 100644
--- a/runtime/vm/compiler/frontend/scope_builder.cc
+++ b/runtime/vm/compiler/frontend/scope_builder.cc
@@ -758,6 +758,13 @@
       }
       return;
     }
+    case kListConcatenation:
+    case kSetConcatenation:
+    case kMapConcatenation:
+      // Collection concatenation operations are removed by the constant
+      // evaluator.
+      UNREACHABLE();
+      break;
     case kIsExpression:
       helper_.ReadPosition();  // read position.
       VisitExpression();       // read operand.
diff --git a/runtime/vm/kernel_binary.h b/runtime/vm/kernel_binary.h
index 7129b5f..28e92be 100644
--- a/runtime/vm/kernel_binary.h
+++ b/runtime/vm/kernel_binary.h
@@ -20,7 +20,7 @@
 
 // Both version numbers are inclusive.
 static const uint32_t kMinSupportedKernelFormatVersion = 18;
-static const uint32_t kMaxSupportedKernelFormatVersion = 20;
+static const uint32_t kMaxSupportedKernelFormatVersion = 21;
 
 // Keep in sync with package:kernel/lib/binary/tag.dart
 #define KERNEL_TAG_LIST(V)                                                     \
@@ -62,6 +62,9 @@
   V(LogicalExpression, 34)                                                     \
   V(ConditionalExpression, 35)                                                 \
   V(StringConcatenation, 36)                                                   \
+  V(ListConcatenation, 111)                                                    \
+  V(SetConcatenation, 112)                                                     \
+  V(MapConcatenation, 113)                                                     \
   V(IsExpression, 37)                                                          \
   V(AsExpression, 38)                                                          \
   V(StringLiteral, 39)                                                         \