[cfe] Add text serialization support for libraries

Change-Id: Ia36efcc433df573abf2f82c1b402fa031276c652
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/143587
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Dmitry Stefantsov <dmitryas@google.com>
diff --git a/pkg/kernel/lib/text/serializer_combinators.dart b/pkg/kernel/lib/text/serializer_combinators.dart
index e166cb2..45de839 100644
--- a/pkg/kernel/lib/text/serializer_combinators.dart
+++ b/pkg/kernel/lib/text/serializer_combinators.dart
@@ -185,6 +185,19 @@
   }
 }
 
+class UriSerializer extends TextSerializer<Uri> {
+  const UriSerializer();
+
+  Uri readFrom(Iterator<Object> stream, DeserializationState state) {
+    String uriAsString = const DartString().readFrom(stream, state);
+    return Uri.parse(uriAsString);
+  }
+
+  void writeTo(StringBuffer buffer, Uri object, SerializationState state) {
+    const DartString().writeTo(buffer, object.toString(), state);
+  }
+}
+
 // == Serializers for tagged (disjoint) unions.
 //
 // They require a function mapping serializables to a tag string.  This is
@@ -195,7 +208,7 @@
   final List<String> tags;
   final List<TextSerializer<T>> serializers;
 
-  Case(this.tagger, this.tags, this.serializers);
+  const Case(this.tagger, this.tags, this.serializers);
 
   Case.uninitialized(this.tagger)
       : tags = [],
@@ -249,7 +262,7 @@
   final K Function(S) wrap;
   final TextSerializer<S> contents;
 
-  Wrapped(this.unwrap, this.wrap, this.contents);
+  const Wrapped(this.unwrap, this.wrap, this.contents);
 
   K readFrom(Iterator<Object> stream, DeserializationState state) {
     return wrap(contents.readFrom(stream, state));
diff --git a/pkg/kernel/lib/text/text_serializer.dart b/pkg/kernel/lib/text/text_serializer.dart
index 467af4d..633e8c6 100644
--- a/pkg/kernel/lib/text/text_serializer.dart
+++ b/pkg/kernel/lib/text/text_serializer.dart
@@ -20,14 +20,14 @@
   String tag(Name name) => name.isPrivate ? "private" : "public";
 }
 
-TextSerializer<Name> publicName =
-    new Wrapped(unwrapPublicName, wrapPublicName, const DartString());
+const TextSerializer<Name> publicName =
+    const Wrapped(unwrapPublicName, wrapPublicName, const DartString());
 
 String unwrapPublicName(Name name) => name.name;
 
 Name wrapPublicName(String name) => new Name(name);
 
-TextSerializer<Name> privateName = new Wrapped(unwrapPrivateName,
+const TextSerializer<Name> privateName = const Wrapped(unwrapPrivateName,
     wrapPrivateName, Tuple2Serializer(const DartString(), const DartString()));
 
 Tuple2<String, String> unwrapPrivateName(Name name) {
@@ -40,7 +40,7 @@
   throw UnimplementedError('deserialization of private names');
 }
 
-TextSerializer<Name> nameSerializer = new Case(const NameTagger(), [
+const TextSerializer<Name> nameSerializer = const Case(const NameTagger(), [
   "public",
   "private",
 ], [
@@ -119,8 +119,9 @@
   String visitFunctionExpression(FunctionExpression _) => "fun";
 }
 
-TextSerializer<InvalidExpression> invalidExpressionSerializer = new Wrapped(
-    unwrapInvalidExpression, wrapInvalidExpression, const DartString());
+const TextSerializer<InvalidExpression> invalidExpressionSerializer =
+    const Wrapped(
+        unwrapInvalidExpression, wrapInvalidExpression, const DartString());
 
 String unwrapInvalidExpression(InvalidExpression expression) {
   return expression.message;
@@ -173,57 +174,57 @@
   return new StringConcatenation(expressions);
 }
 
-TextSerializer<StringLiteral> stringLiteralSerializer =
-    new Wrapped(unwrapStringLiteral, wrapStringLiteral, const DartString());
+const TextSerializer<StringLiteral> stringLiteralSerializer =
+    const Wrapped(unwrapStringLiteral, wrapStringLiteral, const DartString());
 
 String unwrapStringLiteral(StringLiteral literal) => literal.value;
 
 StringLiteral wrapStringLiteral(String value) => new StringLiteral(value);
 
-TextSerializer<IntLiteral> intLiteralSerializer =
-    new Wrapped(unwrapIntLiteral, wrapIntLiteral, const DartInt());
+const TextSerializer<IntLiteral> intLiteralSerializer =
+    const Wrapped(unwrapIntLiteral, wrapIntLiteral, const DartInt());
 
 int unwrapIntLiteral(IntLiteral literal) => literal.value;
 
 IntLiteral wrapIntLiteral(int value) => new IntLiteral(value);
 
-TextSerializer<DoubleLiteral> doubleLiteralSerializer =
-    new Wrapped(unwrapDoubleLiteral, wrapDoubleLiteral, const DartDouble());
+const TextSerializer<DoubleLiteral> doubleLiteralSerializer =
+    const Wrapped(unwrapDoubleLiteral, wrapDoubleLiteral, const DartDouble());
 
 double unwrapDoubleLiteral(DoubleLiteral literal) => literal.value;
 
 DoubleLiteral wrapDoubleLiteral(double value) => new DoubleLiteral(value);
 
-TextSerializer<BoolLiteral> boolLiteralSerializer =
-    new Wrapped(unwrapBoolLiteral, wrapBoolLiteral, const DartBool());
+const TextSerializer<BoolLiteral> boolLiteralSerializer =
+    const Wrapped(unwrapBoolLiteral, wrapBoolLiteral, const DartBool());
 
 bool unwrapBoolLiteral(BoolLiteral literal) => literal.value;
 
 BoolLiteral wrapBoolLiteral(bool value) => new BoolLiteral(value);
 
-TextSerializer<NullLiteral> nullLiteralSerializer =
-    new Wrapped(unwrapNullLiteral, wrapNullLiteral, const Nothing());
+const TextSerializer<NullLiteral> nullLiteralSerializer =
+    const Wrapped(unwrapNullLiteral, wrapNullLiteral, const Nothing());
 
 void unwrapNullLiteral(NullLiteral literal) {}
 
 NullLiteral wrapNullLiteral(void ignored) => new NullLiteral();
 
-TextSerializer<SymbolLiteral> symbolLiteralSerializer =
-    new Wrapped(unwrapSymbolLiteral, wrapSymbolLiteral, const DartString());
+const TextSerializer<SymbolLiteral> symbolLiteralSerializer =
+    const Wrapped(unwrapSymbolLiteral, wrapSymbolLiteral, const DartString());
 
 String unwrapSymbolLiteral(SymbolLiteral expression) => expression.value;
 
 SymbolLiteral wrapSymbolLiteral(String value) => new SymbolLiteral(value);
 
-TextSerializer<ThisExpression> thisExpressionSerializer =
-    new Wrapped(unwrapThisExpression, wrapThisExpression, const Nothing());
+const TextSerializer<ThisExpression> thisExpressionSerializer =
+    const Wrapped(unwrapThisExpression, wrapThisExpression, const Nothing());
 
 void unwrapThisExpression(ThisExpression expression) {}
 
 ThisExpression wrapThisExpression(void ignored) => new ThisExpression();
 
-TextSerializer<Rethrow> rethrowSerializer =
-    new Wrapped(unwrapRethrow, wrapRethrow, const Nothing());
+const TextSerializer<Rethrow> rethrowSerializer =
+    const Wrapped(unwrapRethrow, wrapRethrow, const Nothing());
 
 void unwrapRethrow(Rethrow expression) {}
 
@@ -439,8 +440,8 @@
   return new PropertySet(tuple.first, tuple.second, tuple.third);
 }
 
-TextSerializer<SuperPropertyGet> superPropertyGetSerializer =
-    new Wrapped(unwrapSuperPropertyGet, wrapSuperPropertyGet, nameSerializer);
+const TextSerializer<SuperPropertyGet> superPropertyGetSerializer =
+    const Wrapped(unwrapSuperPropertyGet, wrapSuperPropertyGet, nameSerializer);
 
 Name unwrapSuperPropertyGet(SuperPropertyGet expression) {
   return expression.name;
@@ -553,8 +554,8 @@
   }
 }
 
-TextSerializer<StaticGet> staticGetSerializer =
-    new Wrapped(unwrapStaticGet, wrapStaticGet, new CanonicalNameSerializer());
+const TextSerializer<StaticGet> staticGetSerializer = const Wrapped(
+    unwrapStaticGet, wrapStaticGet, const CanonicalNameSerializer());
 
 CanonicalName unwrapStaticGet(StaticGet expression) {
   return expression.targetReference.canonicalName;
@@ -831,8 +832,8 @@
   constDeclarationSerializer,
 ]);
 
-TextSerializer<TypeParameter> typeParameterSerializer =
-    new Wrapped(unwrapTypeParameter, wrapTypeParameter, const DartString());
+const TextSerializer<TypeParameter> typeParameterSerializer =
+    const Wrapped(unwrapTypeParameter, wrapTypeParameter, const DartString());
 
 String unwrapTypeParameter(TypeParameter node) => node.name;
 
@@ -889,29 +890,29 @@
   String visitTypeParameterType(TypeParameterType _) => "par";
 }
 
-TextSerializer<InvalidType> invalidTypeSerializer =
-    new Wrapped(unwrapInvalidType, wrapInvalidType, const Nothing());
+const TextSerializer<InvalidType> invalidTypeSerializer =
+    const Wrapped(unwrapInvalidType, wrapInvalidType, const Nothing());
 
 void unwrapInvalidType(InvalidType type) {}
 
 InvalidType wrapInvalidType(void ignored) => const InvalidType();
 
-TextSerializer<DynamicType> dynamicTypeSerializer =
-    new Wrapped(unwrapDynamicType, wrapDynamicType, const Nothing());
+const TextSerializer<DynamicType> dynamicTypeSerializer =
+    const Wrapped(unwrapDynamicType, wrapDynamicType, const Nothing());
 
 void unwrapDynamicType(DynamicType type) {}
 
 DynamicType wrapDynamicType(void ignored) => const DynamicType();
 
-TextSerializer<VoidType> voidTypeSerializer =
-    new Wrapped(unwrapVoidType, wrapVoidType, const Nothing());
+const TextSerializer<VoidType> voidTypeSerializer =
+    const Wrapped(unwrapVoidType, wrapVoidType, const Nothing());
 
 void unwrapVoidType(VoidType type) {}
 
 VoidType wrapVoidType(void ignored) => const VoidType();
 
-TextSerializer<BottomType> bottomTypeSerializer =
-    new Wrapped(unwrapBottomType, wrapBottomType, const Nothing());
+const TextSerializer<BottomType> bottomTypeSerializer =
+    const Wrapped(unwrapBottomType, wrapBottomType, const Nothing());
 
 void unwrapBottomType(BottomType type) {}
 
@@ -1258,6 +1259,31 @@
 Case<Procedure> procedureSerializer =
     new Case.uninitialized(const ProcedureTagger());
 
+class LibraryTagger implements Tagger<Library> {
+  const LibraryTagger();
+
+  String tag(Library node) {
+    return node.isNonNullableByDefault ? "null-safe" : "legacy";
+  }
+}
+
+TextSerializer<Library> libraryContentsSerializer = new Wrapped(
+  unwrapLibraryNode,
+  wrapLibraryNode,
+  new Tuple2Serializer(
+      const UriSerializer(), new ListSerializer(procedureSerializer)),
+);
+
+Tuple2<Uri, List<Procedure>> unwrapLibraryNode(Library library) {
+  return new Tuple2(library.importUri, library.procedures);
+}
+
+Library wrapLibraryNode(Tuple2<Uri, List<Procedure>> tuple) {
+  return new Library(tuple.first, procedures: tuple.second);
+}
+
+Case<Library> librarySerializer = new Case.uninitialized(const LibraryTagger());
+
 void initializeSerializers() {
   expressionSerializer.tags.addAll([
     "string",
@@ -1391,4 +1417,9 @@
   ]);
   procedureSerializer.tags.addAll(["static-method"]);
   procedureSerializer.serializers.addAll([staticMethodSerializer]);
+  librarySerializer.tags.addAll(["legacy", "null-safe"]);
+  librarySerializer.serializers.addAll([
+    libraryContentsSerializer,
+    libraryContentsSerializer,
+  ]);
 }
diff --git a/pkg/kernel/test/text_serializer_from_kernel_nodes_test.dart b/pkg/kernel/test/text_serializer_from_kernel_nodes_test.dart
index 2857826..eb2f032 100644
--- a/pkg/kernel/test/text_serializer_from_kernel_nodes_test.dart
+++ b/pkg/kernel/test/text_serializer_from_kernel_nodes_test.dart
@@ -13,87 +13,42 @@
   test();
 }
 
-abstract class TestCase<T extends Node> {
+class TestCase<T extends Node> {
   final String name;
   final T node;
   final SerializationState serializationState;
   final DeserializationState deserializationState;
   final String expectation;
+  final TextSerializer<T> serializer;
 
-  TestCase._(
+  TestCase(
       {this.name,
       this.node,
       this.expectation,
+      this.serializer,
       SerializationState serializationState,
       DeserializationState deserializationState})
-      : this.serializationState =
+      : assert(node != null),
+        assert(expectation != null),
+        assert(serializer != null),
+        this.serializationState =
             serializationState ?? new SerializationState(null),
         this.deserializationState = deserializationState ??
             new DeserializationState(null, new CanonicalName.root());
 
-  T readNode(String input, DeserializationState state);
-
-  String writeNode(T node, SerializationState state);
-}
-
-class StatementTestCase extends TestCase<Statement> {
-  StatementTestCase(
-      {String name,
-      Statement node,
-      String expectation,
-      SerializationState serializationState,
-      DeserializationState deserializationState})
-      : super._(
-            name: name,
-            node: node,
-            expectation: expectation,
-            serializationState: serializationState,
-            deserializationState: deserializationState);
-
-  Statement readNode(String input, DeserializationState state) {
+  T readNode(String input, DeserializationState state) {
     TextIterator stream = new TextIterator(input, 0);
     stream.moveNext();
-    Statement result = statementSerializer.readFrom(stream, state);
+    T result = serializer.readFrom(stream, state);
     if (stream.moveNext()) {
-      throw new StateError("Found extra tokens at the end of the statement.");
+      throw new StateError("Found extra tokens at the end.");
     }
     return result;
   }
 
-  String writeNode(Statement statement, SerializationState state) {
+  String writeNode(T node, SerializationState state) {
     StringBuffer buffer = new StringBuffer();
-    statementSerializer.writeTo(buffer, statement, state);
-    return buffer.toString();
-  }
-}
-
-class ProcedureTestCase extends TestCase<Procedure> {
-  ProcedureTestCase(
-      {String name,
-      Procedure node,
-      String expectation,
-      SerializationState serializationState,
-      DeserializationState deserializationState})
-      : super._(
-            name: name,
-            node: node,
-            expectation: expectation,
-            serializationState: serializationState,
-            deserializationState: deserializationState);
-
-  Procedure readNode(String input, DeserializationState state) {
-    TextIterator stream = new TextIterator(input, 0);
-    stream.moveNext();
-    Procedure result = procedureSerializer.readFrom(stream, state);
-    if (stream.moveNext()) {
-      throw new StateError("Found extra tokens at the end of the procedure.");
-    }
-    return result;
-  }
-
-  String writeNode(Procedure procedure, SerializationState state) {
-    StringBuffer buffer = new StringBuffer();
-    procedureSerializer.writeTo(buffer, procedure, state);
+    serializer.writeTo(buffer, node, state);
     return buffer.toString();
   }
 }
@@ -101,7 +56,7 @@
 void test() {
   List<String> failures = [];
   List<TestCase> tests = <TestCase>[
-    new StatementTestCase(
+    new TestCase<Statement>(
         name: 'let dynamic x = 42 in x;',
         node: () {
           VariableDeclaration x = new VariableDeclaration('x',
@@ -110,8 +65,9 @@
         }(),
         expectation: ''
             '(expr (let (var "x^0" (dynamic) (int 42) ())'
-            ' (get-var "x^0" _)))'),
-    new StatementTestCase(
+            ' (get-var "x^0" _)))',
+        serializer: statementSerializer),
+    new TestCase<Statement>(
         name: 'let dynamic x = 42 in let Bottom x^0 = null in x;',
         node: () {
           VariableDeclaration outterLetVar = new VariableDeclaration('x',
@@ -124,8 +80,9 @@
         expectation: ''
             '(expr (let (var "x^0" (dynamic) (int 42) ())'
             ' (let (var "x^1" (bottom) (null) ())'
-            ' (get-var "x^0" _))))'),
-    new StatementTestCase(
+            ' (get-var "x^0" _))))',
+        serializer: statementSerializer),
+    new TestCase<Statement>(
         name: 'let dynamic x = 42 in let Bottom x^0 = null in x^0;',
         node: () {
           VariableDeclaration outterLetVar = new VariableDeclaration('x',
@@ -138,11 +95,12 @@
         expectation: ''
             '(expr (let (var "x^0" (dynamic) (int 42) ())'
             ' (let (var "x^1" (bottom) (null) ())'
-            ' (get-var "x^1" _))))'),
+            ' (get-var "x^1" _))))',
+        serializer: statementSerializer),
     () {
       VariableDeclaration x =
           new VariableDeclaration('x', type: const DynamicType());
-      return new StatementTestCase(
+      return new TestCase<Statement>(
           name: '/* suppose: dynamic x; */ x = 42;',
           node: new ExpressionStatement(new VariableSet(x, new IntLiteral(42))),
           expectation: '(expr (set-var "x^0" (int 42)))',
@@ -155,7 +113,8 @@
               new DeserializationEnvironment(null)
                 ..addBinder('x^0', x)
                 ..close(),
-              new CanonicalName.root()));
+              new CanonicalName.root()),
+          serializer: statementSerializer);
     }(),
     () {
       Field field = new Field(new Name('field'), type: const DynamicType());
@@ -164,13 +123,14 @@
           fields: <Field>[field]);
       Component component = new Component(libraries: <Library>[library]);
       component.computeCanonicalNames();
-      return new StatementTestCase(
+      return new TestCase<Statement>(
           name: '/* suppose top-level: dynamic field; */ field;',
           node: new ExpressionStatement(new StaticGet(field)),
           expectation: ''
               '(expr (get-static "package:foo/bar.dart::@fields::field"))',
           serializationState: new SerializationState(null),
-          deserializationState: new DeserializationState(null, component.root));
+          deserializationState: new DeserializationState(null, component.root),
+          serializer: statementSerializer);
     }(),
     () {
       Field field = new Field(new Name('field'), type: const DynamicType());
@@ -179,7 +139,7 @@
           fields: <Field>[field]);
       Component component = new Component(libraries: <Library>[library]);
       component.computeCanonicalNames();
-      return new StatementTestCase(
+      return new TestCase<Statement>(
           name: '/* suppose top-level: dynamic field; */ field = 1;',
           node:
               new ExpressionStatement(new StaticSet(field, new IntLiteral(1))),
@@ -187,7 +147,8 @@
               '(expr'
               ' (set-static "package:foo/bar.dart::@fields::field" (int 1)))',
           serializationState: new SerializationState(null),
-          deserializationState: new DeserializationState(null, component.root));
+          deserializationState: new DeserializationState(null, component.root),
+          serializer: statementSerializer);
     }(),
     () {
       Procedure topLevelProcedure = new Procedure(
@@ -202,7 +163,7 @@
           procedures: <Procedure>[topLevelProcedure]);
       Component component = new Component(libraries: <Library>[library]);
       component.computeCanonicalNames();
-      return new StatementTestCase(
+      return new TestCase<Statement>(
           name: '/* suppose top-level: foo(dynamic x) {...}; */ foo(42);',
           node: new ExpressionStatement(new StaticInvocation.byReference(
               topLevelProcedure.reference,
@@ -212,7 +173,8 @@
               '(expr (invoke-static "package:foo/bar.dart::@methods::foo"'
               ' () ((int 42)) ()))',
           serializationState: new SerializationState(null),
-          deserializationState: new DeserializationState(null, component.root));
+          deserializationState: new DeserializationState(null, component.root),
+          serializer: statementSerializer);
     }(),
     () {
       Procedure factoryConstructor = new Procedure(
@@ -225,7 +187,7 @@
           classes: <Class>[klass]);
       Component component = new Component(libraries: <Library>[library]);
       component.computeCanonicalNames();
-      return new StatementTestCase(
+      return new TestCase<Statement>(
           name: ''
               '/* suppose A { const A(); const factory A.foo() = A; } */'
               ' const A.foo();',
@@ -237,7 +199,8 @@
               ' "package:foo/bar.dart::A::@factories::foo"'
               ' () () ()))',
           serializationState: new SerializationState(null),
-          deserializationState: new DeserializationState(null, component.root));
+          deserializationState: new DeserializationState(null, component.root),
+          serializer: statementSerializer);
     }(),
     () {
       Field field = new Field(new Name('field'), type: const DynamicType());
@@ -250,7 +213,7 @@
 
       VariableDeclaration x =
           new VariableDeclaration('x', type: const DynamicType());
-      return new StatementTestCase(
+      return new TestCase<Statement>(
           name: '/* suppose A {dynamic field;} A x; */ x.{A::field};',
           node: new ExpressionStatement(new DirectPropertyGet.byReference(
               new VariableGet(x), field.reference)),
@@ -265,7 +228,8 @@
               new DeserializationEnvironment(null)
                 ..addBinder('x^0', x)
                 ..close(),
-              component.root));
+              component.root),
+          serializer: statementSerializer);
     }(),
     () {
       Field field = new Field(new Name('field'), type: const DynamicType());
@@ -278,7 +242,7 @@
 
       VariableDeclaration x =
           new VariableDeclaration('x', type: const DynamicType());
-      return new StatementTestCase(
+      return new TestCase<Statement>(
           name: '/* suppose A {dynamic field;} A x; */ x.{A::field} = 42;',
           node: new ExpressionStatement(new DirectPropertySet.byReference(
               new VariableGet(x), field.reference, new IntLiteral(42))),
@@ -293,7 +257,8 @@
               new DeserializationEnvironment(null)
                 ..addBinder('x^0', x)
                 ..close(),
-              component.root));
+              component.root),
+          serializer: statementSerializer);
     }(),
     () {
       Procedure method = new Procedure(
@@ -308,7 +273,7 @@
 
       VariableDeclaration x =
           new VariableDeclaration('x', type: const DynamicType());
-      return new StatementTestCase(
+      return new TestCase<Statement>(
           name: '/* suppose A {foo() {...}} A x; */ x.{A::foo}();',
           node: new ExpressionStatement(new DirectMethodInvocation.byReference(
               new VariableGet(x), method.reference, new Arguments([]))),
@@ -324,7 +289,8 @@
               new DeserializationEnvironment(null)
                 ..addBinder('x^0', x)
                 ..close(),
-              component.root));
+              component.root),
+          serializer: statementSerializer);
     }(),
     () {
       Constructor constructor =
@@ -336,7 +302,7 @@
           classes: <Class>[klass]);
       Component component = new Component(libraries: <Library>[library]);
       component.computeCanonicalNames();
-      return new StatementTestCase(
+      return new TestCase<Statement>(
           name: '/* suppose A {A.foo();} */ new A();',
           node: new ExpressionStatement(new ConstructorInvocation.byReference(
               constructor.reference, new Arguments([]))),
@@ -345,7 +311,8 @@
               ' "package:foo/bar.dart::A::@constructors::foo"'
               ' () () ()))',
           serializationState: new SerializationState(null),
-          deserializationState: new DeserializationState(null, component.root));
+          deserializationState: new DeserializationState(null, component.root),
+          serializer: statementSerializer);
     }(),
     () {
       Constructor constructor = new Constructor(new FunctionNode(null),
@@ -357,7 +324,7 @@
           classes: <Class>[klass]);
       Component component = new Component(libraries: <Library>[library]);
       component.computeCanonicalNames();
-      return new StatementTestCase(
+      return new TestCase<Statement>(
           name: '/* suppose A {const A.foo();} */ const A();',
           node: new ExpressionStatement(new ConstructorInvocation.byReference(
               constructor.reference, new Arguments([]),
@@ -367,14 +334,15 @@
               ' "package:foo/bar.dart::A::@constructors::foo"'
               ' () () ()))',
           serializationState: new SerializationState(null),
-          deserializationState: new DeserializationState(null, component.root));
+          deserializationState: new DeserializationState(null, component.root),
+          serializer: statementSerializer);
     }(),
     () {
       TypeParameter outterParam =
           new TypeParameter('T', const DynamicType(), const DynamicType());
       TypeParameter innerParam =
           new TypeParameter('T', const DynamicType(), const DynamicType());
-      return new StatementTestCase(
+      return new TestCase<Statement>(
           name: '/* T Function<T>(T Function<T>()); */',
           node: new ExpressionStatement(new TypeLiteral(new FunctionType(
               [
@@ -394,7 +362,8 @@
           serializationState:
               new SerializationState(new SerializationEnvironment(null)),
           deserializationState: new DeserializationState(
-              new DeserializationEnvironment(null), null));
+              new DeserializationEnvironment(null), null),
+          serializer: statementSerializer);
     }(),
     () {
       TypeParameter t =
@@ -403,7 +372,7 @@
           type: new TypeParameterType(t, Nullability.legacy));
       VariableDeclaration t2 = new VariableDeclaration('t2',
           type: new TypeParameterType(t, Nullability.legacy));
-      return new StatementTestCase(
+      return new TestCase<Statement>(
           name: '/* <T>(T t1, [T t2]) => t1; */',
           node: new ExpressionStatement(new FunctionExpression(new FunctionNode(
               new ReturnStatement(new VariableGet(t1)),
@@ -420,7 +389,8 @@
           serializationState:
               new SerializationState(new SerializationEnvironment(null)),
           deserializationState: new DeserializationState(
-              new DeserializationEnvironment(null), null));
+              new DeserializationEnvironment(null), null),
+          serializer: statementSerializer);
     }(),
     () {
       VariableDeclaration x = VariableDeclaration('x', type: DynamicType());
@@ -434,16 +404,59 @@
           procedures: [foo]);
       Component component = Component(libraries: [library]);
       component.computeCanonicalNames();
-      return new ProcedureTestCase(
+      return new TestCase<Procedure>(
           name: 'foo(x) => x;',
           node: foo,
           expectation: ''
               '(static-method (public "foo")'
-              ' (sync () () () ((var "x^0" (dynamic) _ ())) () () (dynamic) (ret (get-var "x^0" _))))',
+              ' (sync () () () ((var "x^0" (dynamic) _ ())) () ()'
+              ' (dynamic) (ret (get-var "x^0" _))))',
           serializationState:
               new SerializationState(new SerializationEnvironment(null)),
           deserializationState: new DeserializationState(
-              new DeserializationEnvironment(null), null));
+              new DeserializationEnvironment(null), null),
+          serializer: procedureSerializer);
+    }(),
+    () {
+      VariableDeclaration x1 = VariableDeclaration('x', type: DynamicType());
+      VariableDeclaration x2 = VariableDeclaration('x', type: DynamicType());
+      Procedure foo = Procedure(
+          Name('foo'),
+          ProcedureKind.Method,
+          FunctionNode(ReturnStatement(VariableGet(x1)),
+              positionalParameters: [x1]),
+          isStatic: true);
+      Procedure bar = Procedure(
+          Name('bar'),
+          ProcedureKind.Method,
+          FunctionNode(
+              ReturnStatement(
+                  StaticInvocation(foo, Arguments([VariableGet(x2)]))),
+              positionalParameters: [x2]),
+          isStatic: true);
+      Library library = Library(Uri(scheme: 'package', path: 'foo/bar.dart'),
+          procedures: [foo, bar]);
+      Component component = Component(libraries: [library]);
+      component.computeCanonicalNames();
+      return new TestCase<Library>(
+          name: 'foo(x) => x; bar(x) => foo(x);',
+          node: library,
+          expectation: ''
+              '(legacy "package:foo/bar.dart"'
+              ''
+              ' ((static-method (public "foo")'
+              ' (sync () () () ((var "x^0" (dynamic) _ ())) () () (dynamic)'
+              ' (ret (get-var "x^0" _))))'
+              ''
+              ' (static-method (public "bar")'
+              ' (sync () () () ((var "x^0" (dynamic) _ ())) () () (dynamic)'
+              ' (ret (invoke-static "package:foo/bar.dart::@methods::foo"'
+              ' () ((get-var "x^0" _)) ()))))))',
+          serializationState:
+              new SerializationState(new SerializationEnvironment(null)),
+          deserializationState: new DeserializationState(
+              new DeserializationEnvironment(null), new CanonicalName.root()),
+          serializer: librarySerializer);
     }(),
   ];
   for (TestCase testCase in tests) {
@@ -451,8 +464,8 @@
         testCase.writeNode(testCase.node, testCase.serializationState);
     if (roundTripInput != testCase.expectation) {
       failures.add(''
-          '* initial serialization for test "${testCase.name}"'
-          ' gave output "${roundTripInput}"');
+          "* initial serialization for test '${testCase.name}'"
+          " gave output '${roundTripInput}'");
     }
 
     TreeNode deserialized =
@@ -461,7 +474,7 @@
         testCase.writeNode(deserialized, testCase.serializationState);
     if (roundTripOutput != roundTripInput) {
       failures.add(''
-          '* input "${testCase.name}" gave output "${roundTripOutput}"');
+          "* input '${testCase.name}' gave output '${roundTripOutput}'");
     }
   }
   if (failures.isNotEmpty) {