[dart2js] Prettier printing of powerset values

Print the bits with names, e.g.

    {null,other}[null|exact=JSString]
    {true,false}[exact=JSBool]

Change-Id: I4e940ae903329ca550ea9ff4afebd6eb59c0d62d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/160480
Reviewed-by: Christian Altamirano <coam@google.com>
Reviewed-by: Stephen Adams <sra@google.com>
Commit-Queue: Stephen Adams <sra@google.com>
diff --git a/pkg/compiler/lib/src/inferrer/powersets/powerset_bits.dart b/pkg/compiler/lib/src/inferrer/powersets/powerset_bits.dart
index 193e37c..a7304ff 100644
--- a/pkg/compiler/lib/src/inferrer/powersets/powerset_bits.dart
+++ b/pkg/compiler/lib/src/inferrer/powersets/powerset_bits.dart
@@ -36,12 +36,15 @@
   static const int _otherIndex = 3;
 
   static const int _maxIndex = _otherIndex;
+
   static const List<int> _singletonIndices = [
     _trueIndex,
     _falseIndex,
     _nullIndex,
   ];
 
+  static const List<String> _bitNames = ['true', 'false', 'null', 'other'];
+
   PowersetBitsDomain(this._closedWorld);
 
   CommonElements get commonElements => _closedWorld.commonElements;
@@ -87,6 +90,33 @@
     return AbstractBool.Maybe;
   }
 
+  /// Returns a descriptive string for [bits]
+  static String toText(int bits, {bool omitIfTop = false}) {
+    int boolDomainMask = (1 << _maxIndex + 1) - 1;
+    return _toTextDomain(bits, boolDomainMask, omitIfTop);
+  }
+
+  /// Returns a descriptive string for a subset of [bits] defined by
+  /// [domainMask]. If [omitIfTop] is `true` and all the bits in the
+  /// [domainMask] are set, an empty string is returned.
+  static String _toTextDomain(int bits, int domainMask, bool omitIfTop) {
+    bits &= domainMask;
+    if (bits == domainMask && omitIfTop) return '';
+    final sb = StringBuffer();
+    sb.write('{');
+    String comma = '';
+    while (bits != 0) {
+      int lowestBit = bits & ~(bits - 1);
+      int index = lowestBit.bitLength - 1;
+      sb.write(comma);
+      sb.write(_bitNames[index]);
+      comma = ',';
+      bits &= ~lowestBit;
+    }
+    sb.write('}');
+    return '$sb';
+  }
+
   AbstractBool needsNoSuchMethodHandling(int receiver, Selector selector) =>
       AbstractBool.Maybe;
 
diff --git a/pkg/compiler/lib/src/inferrer/powersets/powersets.dart b/pkg/compiler/lib/src/inferrer/powersets/powersets.dart
index 746a3c7..5f2fb1e 100644
--- a/pkg/compiler/lib/src/inferrer/powersets/powersets.dart
+++ b/pkg/compiler/lib/src/inferrer/powersets/powersets.dart
@@ -40,7 +40,8 @@
 
   @override
   String toString() =>
-      '[Powerset of ${_abstractValue.toString()} with bits ${_powersetBits}]';
+      '${PowersetBitsDomain.toText(_powersetBits, omitIfTop: true)}'
+      '${_abstractValue}';
 }
 
 AbstractValue unwrapOrNull(PowersetValue powerset) {