New printer for types and constants

Adds labels to ambiguous types and generates a bullet list of the
origins of all raw types contained in printed types.

Change-Id: I9b8a46aa8b1f463c6cd02b9df042075fc1c46404
Reviewed-on: https://dart-review.googlesource.com/c/84602
Reviewed-by: Peter von der Ahé <ahe@google.com>
diff --git a/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart b/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart
index 5e23ccb..efef374 100644
--- a/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart
+++ b/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart
@@ -8476,6 +8476,55 @@
 }
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Template<Message Function(String name, Uri uri_)> templateTypeOrigin =
+    const Template<Message Function(String name, Uri uri_)>(
+        messageTemplate: r"""'#name' is from '#uri'.""",
+        withArguments: _withArgumentsTypeOrigin);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Message Function(String name, Uri uri_)> codeTypeOrigin =
+    const Code<Message Function(String name, Uri uri_)>(
+  "TypeOrigin",
+  templateTypeOrigin,
+);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+Message _withArgumentsTypeOrigin(String name, Uri uri_) {
+  if (name.isEmpty) throw 'No name provided';
+  name = demangleMixinApplicationName(name);
+  String uri = relativizeUri(uri_);
+  return new Message(codeTypeOrigin,
+      message: """'${name}' is from '${uri}'.""",
+      arguments: {'name': name, 'uri': uri_});
+}
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Template<Message Function(String name, Uri uri_, Uri uri2_)>
+    templateTypeOriginWithFileUri =
+    const Template<Message Function(String name, Uri uri_, Uri uri2_)>(
+        messageTemplate: r"""'#name' is from '#uri' ('#uri2').""",
+        withArguments: _withArgumentsTypeOriginWithFileUri);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Message Function(String name, Uri uri_, Uri uri2_)>
+    codeTypeOriginWithFileUri =
+    const Code<Message Function(String name, Uri uri_, Uri uri2_)>(
+  "TypeOriginWithFileUri",
+  templateTypeOriginWithFileUri,
+);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+Message _withArgumentsTypeOriginWithFileUri(String name, Uri uri_, Uri uri2_) {
+  if (name.isEmpty) throw 'No name provided';
+  name = demangleMixinApplicationName(name);
+  String uri = relativizeUri(uri_);
+  String uri2 = relativizeUri(uri2_);
+  return new Message(codeTypeOriginWithFileUri,
+      message: """'${name}' is from '${uri}' ('${uri2}').""",
+      arguments: {'name': name, 'uri': uri_, 'uri2': uri2_});
+}
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Code<Null> codeTypeVariableDuplicatedName =
     messageTypeVariableDuplicatedName;
 
diff --git a/pkg/front_end/lib/src/fasta/kernel/type_labeler.dart b/pkg/front_end/lib/src/fasta/kernel/type_labeler.dart
new file mode 100644
index 0000000..ffa60c7
--- /dev/null
+++ b/pkg/front_end/lib/src/fasta/kernel/type_labeler.dart
@@ -0,0 +1,323 @@
+// Copyright (c) 2018, 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 file.
+
+import 'dart:convert' show json;
+
+import 'package:kernel/ast.dart'
+    show
+        BoolConstant,
+        BottomType,
+        Class,
+        Constant,
+        ConstantMapEntry,
+        DartType,
+        DoubleConstant,
+        DynamicType,
+        Field,
+        FunctionType,
+        InvalidType,
+        InstanceConstant,
+        IntConstant,
+        InterfaceType,
+        ListConstant,
+        MapConstant,
+        NullConstant,
+        PartialInstantiationConstant,
+        Procedure,
+        StringConstant,
+        SymbolConstant,
+        TearOffConstant,
+        TypedefType,
+        TypeLiteralConstant,
+        TypeParameter,
+        TypeParameterType,
+        VoidType;
+
+import 'package:kernel/visitor.dart' show ConstantVisitor, DartTypeVisitor;
+
+import '../blacklisted_classes.dart' show blacklistedCoreClasses;
+
+import '../fasta_codes.dart'
+    show Message, templateTypeOrigin, templateTypeOriginWithFileUri;
+
+/// A pretty-printer for Kernel types and constants with the ability to label
+/// raw types with numeric markers in Dart comments (e.g. `/*1*/`) to
+/// distinguish different types with the same name. This is used in diagnostic
+/// messages to indicate the origins of types occurring in the message.
+class TypeLabeler
+    implements DartTypeVisitor<List<Object>>, ConstantVisitor<List<Object>> {
+  List<LabeledClassName> names = <LabeledClassName>[];
+  Map<String, List<LabeledClassName>> nameMap =
+      <String, List<LabeledClassName>>{};
+
+  /// Pretty-print a type.
+  /// When all types and constants appearing in the same message have been
+  /// pretty-printed, the returned list can be converted to its string
+  /// representation (with labels on duplicated names) by the `join()` method.
+  List<Object> labelType(DartType type) {
+    // TODO(askesc): Remove null check when we are completely null clean here.
+    if (type == null) return ["null-type"];
+    return type.accept(this);
+  }
+
+  /// Pretty-print a constant.
+  /// When all types and constants appearing in the same message have been
+  /// pretty-printed, the returned list can be converted to its string
+  /// representation (with labels on duplicated names) by the `join()` method.
+  List<Object> labelConstant(Constant constant) {
+    // TODO(askesc): Remove null check when we are completely null clean here.
+    if (constant == null) return ["null-constant"];
+    return constant.accept(this);
+  }
+
+  /// Get a textual description of the origins of the raw types appearing in
+  /// types and constants that have been pretty-printed using this labeler.
+  String get originMessages {
+    StringBuffer messages = new StringBuffer();
+    for (LabeledClassName name in names) {
+      messages.write(name.originMessage);
+    }
+    return messages.toString();
+  }
+
+  // We don't have access to coreTypes here, so we have our own Object check.
+  static bool isObject(DartType type) {
+    if (type is InterfaceType && type.classNode.name == 'Object') {
+      Uri importUri = type.classNode.enclosingLibrary.importUri;
+      return importUri.scheme == 'dart' && importUri.path == 'core';
+    }
+    return false;
+  }
+
+  LabeledClassName nameForClass(Class classNode) {
+    List<LabeledClassName> classesForName = nameMap[classNode.name];
+    if (classesForName == null) {
+      // First encountered class with this name
+      LabeledClassName name = new LabeledClassName(classNode, this);
+      names.add(name);
+      nameMap[classNode.name] = [name];
+      return name;
+    } else {
+      for (LabeledClassName classForName in classesForName) {
+        if (classForName.classNode == classNode) {
+          // Previously encountered class
+          return classForName;
+        }
+      }
+      // New class with name that was previously encountered
+      LabeledClassName name = new LabeledClassName(classNode, this);
+      names.add(name);
+      classesForName.add(name);
+      return name;
+    }
+  }
+
+  List<Object> defaultDartType(DartType type) => null;
+  List<Object> visitTypedefType(TypedefType node) => null;
+
+  // TODO(askesc): Throw internal error if InvalidType appears in diagnostics.
+  List<Object> visitInvalidType(InvalidType node) => ["invalid-type"];
+
+  // TODO(askesc): Throw internal error if BottomType appears in diagnostics.
+  List<Object> visitBottomType(BottomType node) => ["bottom-type"];
+
+  List<Object> visitDynamicType(DynamicType node) => ["dynamic"];
+  List<Object> visitVoidType(VoidType node) => ["void"];
+  List<Object> visitTypeParameterType(TypeParameterType node) =>
+      [node.parameter.name];
+
+  List<Object> visitFunctionType(FunctionType node) {
+    List<Object> result = node.returnType.accept(this);
+    result.add(" Function");
+    if (node.typeParameters.isNotEmpty) {
+      result.add("<");
+      bool first = true;
+      for (TypeParameter param in node.typeParameters) {
+        if (!first) result.add(", ");
+        result.add(param.name);
+        if (isObject(param.bound) && param.defaultType is DynamicType) {
+          // Bound was not specified, and therefore should not be printed.
+        } else {
+          result.add(" extends ");
+          result.addAll(param.bound.accept(this));
+        }
+      }
+      result.add(">");
+    }
+    result.add("(");
+    bool first = true;
+    for (int i = 0; i < node.requiredParameterCount; i++) {
+      if (!first) result.add(", ");
+      result.addAll(node.positionalParameters[i].accept(this));
+      first = false;
+    }
+    if (node.positionalParameters.length > node.requiredParameterCount) {
+      if (node.requiredParameterCount > 0) result.add(", ");
+      result.add("[");
+      first = true;
+      for (int i = node.requiredParameterCount;
+          i < node.positionalParameters.length;
+          i++) {
+        if (!first) result.add(", ");
+        result.addAll(node.positionalParameters[i].accept(this));
+        first = false;
+      }
+      result.add("]");
+    }
+    if (node.namedParameters.isNotEmpty) {
+      if (node.positionalParameters.isNotEmpty) result.add(", ");
+      result.add("{");
+      first = true;
+      for (int i = 0; i < node.namedParameters.length; i++) {
+        if (!first) result.add(", ");
+        result.addAll(node.namedParameters[i].type.accept(this));
+        result.add(" ${node.namedParameters[i].name}");
+        first = false;
+      }
+      result.add("}");
+    }
+    result.add(")");
+    return result;
+  }
+
+  List<Object> visitInterfaceType(InterfaceType node) {
+    List<Object> result = [nameForClass(node.classNode)];
+    if (node.typeArguments.isNotEmpty) {
+      result.add("<");
+      bool first = true;
+      for (DartType typeArg in node.typeArguments) {
+        if (!first) result.add(", ");
+        result.addAll(typeArg.accept(this));
+        first = false;
+      }
+      result.add(">");
+    }
+    return result;
+  }
+
+  List<Object> defaultConstant(Constant node) => null;
+
+  List<Object> visitNullConstant(NullConstant node) => [node];
+  List<Object> visitBoolConstant(BoolConstant node) => [node];
+  List<Object> visitIntConstant(IntConstant node) => [node];
+  List<Object> visitDoubleConstant(DoubleConstant node) => [node];
+  List<Object> visitSymbolConstant(SymbolConstant node) => [node];
+  List<Object> visitStringConstant(StringConstant node) =>
+      [json.encode(node.value)];
+
+  List<Object> visitInstanceConstant(InstanceConstant node) {
+    List<Object> result =
+        new InterfaceType(node.klass, node.typeArguments).accept(this);
+    result.add(" {");
+    bool first = true;
+    for (Field field in node.klass.fields) {
+      if (!first) result.add(", ");
+      result.add("${field.name}: ");
+      result.addAll(node.fieldValues[field.reference].accept(this));
+      first = false;
+    }
+    result.add("}");
+    return result;
+  }
+
+  List<Object> visitListConstant(ListConstant node) {
+    List<Object> result = ["<"];
+    result.addAll(node.typeArgument.accept(this));
+    result.add(">[");
+    bool first = true;
+    for (Constant constant in node.entries) {
+      if (!first) result.add(", ");
+      result.addAll(constant.accept(this));
+      first = false;
+    }
+    result.add("]");
+    return result;
+  }
+
+  List<Object> visitMapConstant(MapConstant node) {
+    List<Object> result = ["<"];
+    result.addAll(node.keyType.accept(this));
+    result.add(", ");
+    result.addAll(node.valueType.accept(this));
+    result.add(">{");
+    bool first = true;
+    for (ConstantMapEntry entry in node.entries) {
+      if (!first) result.add(", ");
+      result.addAll(entry.key.accept(this));
+      result.add(": ");
+      result.addAll(entry.value.accept(this));
+      first = false;
+    }
+    result.add("}");
+    return result;
+  }
+
+  List<Object> visitTearOffConstant(TearOffConstant node) {
+    List<Object> result = [];
+    Procedure procedure = node.procedure;
+    Class classNode = procedure.enclosingClass;
+    if (classNode != null) {
+      result.add(nameForClass(classNode));
+      result.add(".");
+    }
+    result.add(procedure.name.name);
+    return result;
+  }
+
+  List<Object> visitPartialInstantiationConstant(
+      PartialInstantiationConstant node) {
+    List<Object> result = node.tearOffConstant.accept(this);
+    if (node.types.isNotEmpty) {
+      result.add("<");
+      bool first = true;
+      for (DartType typeArg in node.types) {
+        if (!first) result.add(", ");
+        result.addAll(typeArg.accept(this));
+        first = false;
+      }
+      result.add(">");
+    }
+    return result;
+  }
+
+  List<Object> visitTypeLiteralConstant(TypeLiteralConstant node) {
+    return node.type.accept(this);
+  }
+}
+
+class LabeledClassName {
+  Class classNode;
+  TypeLabeler typeLabeler;
+
+  LabeledClassName(this.classNode, this.typeLabeler);
+
+  String toString() {
+    String name = classNode.name;
+    List<LabeledClassName> classesForName = typeLabeler.nameMap[name];
+    if (classesForName.length == 1) {
+      return name;
+    }
+    return "$name/*${classesForName.indexOf(this) + 1}*/";
+  }
+
+  String get originMessage {
+    Uri importUri = classNode.enclosingLibrary.importUri;
+    if (importUri.scheme == 'dart' && importUri.path == 'core') {
+      String name = classNode.name;
+      if (blacklistedCoreClasses.contains(name)) {
+        // Blacklisted core class. Only print if ambiguous.
+        List<LabeledClassName> classesForName = typeLabeler.nameMap[name];
+        if (classesForName.length == 1) {
+          return "";
+        }
+      }
+    }
+    Uri fileUri = classNode.enclosingLibrary.fileUri;
+    Message message = (importUri == fileUri || importUri.scheme == 'dart')
+        ? templateTypeOrigin.withArguments(toString(), importUri)
+        : templateTypeOriginWithFileUri.withArguments(
+            toString(), importUri, fileUri);
+    return "\n - " + message.message;
+  }
+}
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index f80e641..8efaefb 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -3308,3 +3308,15 @@
     foo<X>(X x) => null;
     bar<Y>(Y y) => null;
     main() { foo(bar); }
+
+# These two message templates are used for constructing supplemental text
+# about the origins of raw interface types in error messages containing types.
+TypeOrigin:
+  template: "'#name' is from '#uri'."
+  frontendInternal: true
+  external: test/type_labeler_test.dart
+
+TypeOriginWithFileUri:
+  template: "'#name' is from '#uri' ('#uri2')."
+  frontendInternal: true
+  external: test/type_labeler_test.dart
diff --git a/pkg/front_end/test/type_labeler_test.dart b/pkg/front_end/test/type_labeler_test.dart
new file mode 100644
index 0000000..f24c86d
--- /dev/null
+++ b/pkg/front_end/test/type_labeler_test.dart
@@ -0,0 +1,248 @@
+// Copyright (c) 2018, 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 file.
+
+import 'package:kernel/ast.dart';
+
+import 'package:front_end/src/fasta/kernel/type_labeler.dart';
+
+import 'package:expect/expect.dart';
+
+main() {
+  void check(Map<Node, String> expectations, int bulletCount) {
+    TypeLabeler labeler = new TypeLabeler();
+    Map<Node, List<Object>> conversions = {};
+    expectations.forEach((Node node, String expected) {
+      if (node is DartType) {
+        conversions[node] = labeler.labelType(node);
+      } else if (node is Constant) {
+        conversions[node] = labeler.labelConstant(node);
+      } else {
+        Expect.fail("Neither type nor constant");
+      }
+    });
+    expectations.forEach((Node node, String expected) {
+      Expect.stringEquals(expected, conversions[node].join());
+    });
+    int newlines = "\n".allMatches(labeler.originMessages).length;
+    Expect.equals(bulletCount, newlines);
+  }
+
+  // Library mocks
+  Library dartCoreLib = new Library(new Uri(scheme: 'dart', path: 'core'));
+  Library myLib = new Library(Uri.parse("org-dartlang-testcase:///mylib.dart"));
+
+  // Set up some classes
+  Class objectClass = new Class(name: "Object")..parent = dartCoreLib;
+  Supertype objectSuper = new Supertype(objectClass, []);
+  Class boolClass = new Class(name: "bool", supertype: objectSuper)
+    ..parent = dartCoreLib;
+  Class numClass = new Class(name: "num", supertype: objectSuper)
+    ..parent = dartCoreLib;
+  Supertype numSuper = new Supertype(numClass, []);
+  Class intClass = new Class(name: "int", supertype: numSuper)
+    ..parent = dartCoreLib;
+  Class fooClass = new Class(name: "Foo", supertype: objectSuper)
+    ..parent = myLib;
+  Class foo2Class = new Class(name: "Foo", supertype: objectSuper)
+    ..parent = myLib;
+  Class barClass = new Class(
+      name: "Bar",
+      supertype: objectSuper,
+      typeParameters: [new TypeParameter("X")])
+    ..parent = myLib;
+  Class bazClass = new Class(
+      name: "Baz",
+      supertype: objectSuper,
+      typeParameters: [new TypeParameter("X"), new TypeParameter("Y")])
+    ..parent = myLib;
+
+  // Test types
+  DartType voidType = const VoidType();
+  check({voidType: "void"}, 0);
+
+  DartType dynamicType = const DynamicType();
+  check({dynamicType: "dynamic"}, 0);
+
+  DartType boolType = new InterfaceType(boolClass);
+  check({boolType: "bool"}, 0);
+
+  DartType numType = new InterfaceType(numClass);
+  check({numType: "num"}, 0);
+
+  DartType intType = new InterfaceType(intClass);
+  check({intType: "int"}, 0);
+
+  DartType object = new InterfaceType(objectClass);
+  check({object: "Object"}, 1);
+
+  DartType foo = new InterfaceType(fooClass);
+  check({foo: "Foo"}, 1);
+
+  DartType foo2 = new InterfaceType(foo2Class);
+  check({foo2: "Foo"}, 1);
+  check({foo: "Foo/*1*/", foo2: "Foo/*2*/"}, 2);
+
+  DartType barVoid = new InterfaceType(barClass, [voidType]);
+  check({barVoid: "Bar<void>"}, 1);
+
+  DartType barObject = new InterfaceType(barClass, [object]);
+  check({barObject: "Bar<Object>"}, 2);
+
+  DartType barBarDynamic = new InterfaceType(barClass, [
+    new InterfaceType(barClass, [dynamicType])
+  ]);
+  check({barBarDynamic: "Bar<Bar<dynamic>>"}, 1);
+
+  DartType parameterY = new TypeParameterType(new TypeParameter("Y"));
+  DartType barY = new InterfaceType(barClass, [parameterY]);
+  check({parameterY: "Y", barY: "Bar<Y>"}, 1);
+
+  DartType bazFooBarBazDynamicVoid = new InterfaceType(bazClass, [
+    foo,
+    new InterfaceType(barClass, [
+      new InterfaceType(bazClass, [dynamicType, voidType])
+    ])
+  ]);
+  check({bazFooBarBazDynamicVoid: "Baz<Foo, Bar<Baz<dynamic, void>>>"}, 3);
+
+  DartType bazFooFoo2 = new InterfaceType(bazClass, [foo, foo2]);
+  check({bazFooFoo2: "Baz<Foo/*1*/, Foo/*2*/>"}, 3);
+
+  DartType funVoid = new FunctionType([], voidType);
+  check({funVoid: "void Function()"}, 0);
+
+  DartType funFooBarVoid = new FunctionType([foo], barVoid);
+  check({funFooBarVoid: "Bar<void> Function(Foo)"}, 2);
+
+  DartType funFooFoo2 = new FunctionType([foo], foo2);
+  check({funFooFoo2: "Foo/*1*/ Function(Foo/*2*/)"}, 2);
+
+  DartType funOptFooVoid =
+      new FunctionType([foo], voidType, requiredParameterCount: 0);
+  check({funOptFooVoid: "void Function([Foo])"}, 1);
+
+  DartType funFooOptIntVoid =
+      new FunctionType([foo, intType], voidType, requiredParameterCount: 1);
+  check({funFooOptIntVoid: "void Function(Foo, [int])"}, 1);
+
+  DartType funOptFooOptIntVoid =
+      new FunctionType([foo, intType], voidType, requiredParameterCount: 0);
+  check({funOptFooOptIntVoid: "void Function([Foo, int])"}, 1);
+
+  DartType funNamedObjectVoid = new FunctionType([], voidType,
+      namedParameters: [new NamedType("obj", object)]);
+  check({funNamedObjectVoid: "void Function({Object obj})"}, 1);
+
+  DartType funFooNamedObjectVoid = new FunctionType([foo], voidType,
+      namedParameters: [new NamedType("obj", object)]);
+  check({funFooNamedObjectVoid: "void Function(Foo, {Object obj})"}, 2);
+
+  TypeParameter t = new TypeParameter("T", object, dynamicType);
+  DartType funGeneric = new FunctionType(
+      [new TypeParameterType(t)], new TypeParameterType(t),
+      typeParameters: [t]);
+  check({funGeneric: "T Function<T>(T)"}, 0);
+
+  TypeParameter tObject = new TypeParameter("T", object, object);
+  DartType funGenericObject = new FunctionType(
+      [new TypeParameterType(tObject)], new TypeParameterType(tObject),
+      typeParameters: [tObject]);
+  check({funGenericObject: "T Function<T extends Object>(T)"}, 1);
+
+  TypeParameter tFoo = new TypeParameter("T", foo, dynamicType);
+  DartType funGenericFoo = new FunctionType(
+      [new TypeParameterType(tFoo)], new TypeParameterType(tFoo),
+      typeParameters: [tFoo]);
+  check({funGenericFoo: "T Function<T extends Foo>(T)"}, 1);
+
+  TypeParameter tBar = new TypeParameter("T", dynamicType, dynamicType);
+  tBar.bound = new InterfaceType(barClass, [new TypeParameterType(tBar)]);
+  DartType funGenericBar = new FunctionType(
+      [new TypeParameterType(tBar)], new TypeParameterType(tBar),
+      typeParameters: [tBar]);
+  check({funGenericBar: "T Function<T extends Bar<T>>(T)"}, 1);
+
+  // Add some members for testing instance constants
+  Field booField = new Field(new Name("boo"), type: boolType);
+  fooClass.fields.add(booField);
+  Field valueField = new Field(new Name("value"), type: intType);
+  foo2Class.fields.add(valueField);
+  Field nextField = new Field(new Name("next"), type: foo2);
+  foo2Class.fields.add(nextField);
+  Field xField = new Field(new Name("x"),
+      type: new TypeParameterType(bazClass.typeParameters[0]));
+  bazClass.fields.add(xField);
+  Field yField = new Field(new Name("y"),
+      type: new TypeParameterType(bazClass.typeParameters[1]));
+  bazClass.fields.add(yField);
+  FunctionNode gooFunction = new FunctionNode(new EmptyStatement(),
+      typeParameters: [new TypeParameter("V")]);
+  Procedure gooMethod = new Procedure(
+      new Name("goo"), ProcedureKind.Method, gooFunction,
+      isStatic: true)
+    ..parent = fooClass;
+
+  // Test constants
+  Constant nullConst = new NullConstant();
+  check({nullConst: "null"}, 0);
+
+  Constant trueConst = new BoolConstant(true);
+  Constant falseConst = new BoolConstant(false);
+  check({trueConst: "true", falseConst: "false"}, 0);
+
+  Constant intConst = new IntConstant(2);
+  Constant doubleConst = new DoubleConstant(2.5);
+  check({intConst: "2", doubleConst: "2.5"}, 0);
+
+  Constant stringConst = new StringConstant("Don't \"quote\" me on that!");
+  check({stringConst: "\"Don't \\\"quote\\\" me on that!\""}, 0);
+
+  Constant symConst = new SymbolConstant("foo", null);
+  Constant symLibConst = new SymbolConstant("bar", dartCoreLib.reference);
+  check({symConst: "#foo", symLibConst: "#dart:core::bar"}, 0);
+
+  Constant fooConst = new InstanceConstant(
+      fooClass.reference, [], {booField.reference: trueConst});
+  check({fooConst: "Foo {boo: true}"}, 1);
+
+  Constant foo2Const = new InstanceConstant(foo2Class.reference, [],
+      {nextField.reference: nullConst, valueField.reference: intConst});
+  check({foo2Const: "Foo {value: 2, next: null}"}, 1);
+
+  Constant foo2nConst = new InstanceConstant(foo2Class.reference, [], {
+    valueField.reference: intConst,
+    nextField.reference: new InstanceConstant(foo2Class.reference, [],
+        {valueField.reference: intConst, nextField.reference: nullConst}),
+  });
+  check({foo2nConst: "Foo {value: 2, next: Foo {value: 2, next: null}}"}, 1);
+
+  Constant bazFooFoo2Const = new InstanceConstant(bazClass.reference,
+      [foo, foo2], {xField.reference: fooConst, yField.reference: foo2Const});
+  check({
+    bazFooFoo2Const: "Baz<Foo/*1*/, Foo/*2*/> " +
+        "{x: Foo/*1*/ {boo: true}, y: Foo/*2*/ {value: 2, next: null}}"
+  }, 3);
+
+  Constant listConst = new ListConstant(dynamicType, [intConst, doubleConst]);
+  check({listConst: "<dynamic>[2, 2.5]"}, 0);
+
+  Constant listBoolConst = new ListConstant(boolType, [falseConst, trueConst]);
+  check({listBoolConst: "<bool>[false, true]"}, 0);
+
+  Constant mapConst = new MapConstant(boolType, numType, [
+    new ConstantMapEntry(trueConst, intConst),
+    new ConstantMapEntry(falseConst, doubleConst)
+  ]);
+  check({mapConst: "<bool, num>{true: 2, false: 2.5}"}, 0);
+
+  Constant tearOffConst = new TearOffConstant(gooMethod);
+  check({tearOffConst: "Foo.goo"}, 1);
+
+  Constant partialInstantiationConst =
+      new PartialInstantiationConstant(tearOffConst, [intType]);
+  check({partialInstantiationConst: "Foo.goo<int>"}, 1);
+
+  Constant typeLiteralConst = new TypeLiteralConstant(foo);
+  check({typeLiteralConst: "Foo"}, 1);
+}