Compare types between analyzer and front_end.

Change-Id: I8d0c0eba60a2c2a1fe5a3e4e0c72249941d252d6
Reviewed-on: https://dart-review.googlesource.com/72880
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/analyzer_fe_comparison/lib/src/analyzer.dart b/pkg/analyzer_fe_comparison/lib/src/analyzer.dart
index b0aff01..207ede3 100644
--- a/pkg/analyzer_fe_comparison/lib/src/analyzer.dart
+++ b/pkg/analyzer_fe_comparison/lib/src/analyzer.dart
@@ -7,6 +7,9 @@
 import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/src/generated/resolver.dart';
 import 'package:analyzer/src/generated/source.dart' show SourceKind;
 import 'package:analyzer_fe_comparison/src/comparison_node.dart';
 
@@ -20,6 +23,7 @@
   }
   var context = contexts[0];
   var session = context.currentSession;
+  var typeProvider = await session.typeProvider;
   var uriConverter = session.uriConverter;
   var contextRoot = context.contextRoot;
   var libraryNodes = <ComparisonNode>[];
@@ -35,7 +39,8 @@
       for (var compilationUnit in libraryElement.units) {
         var unitResult =
             await session.getResolvedAst(compilationUnit.source.fullName);
-        _AnalyzerVisitor(childNodes)._visitList(unitResult.unit.declarations);
+        _AnalyzerVisitor(typeProvider, childNodes)
+            ._visitList(unitResult.unit.declarations);
       }
       libraryNodes.add(ComparisonNode.sorted(importUri.toString(), childNodes));
     }
@@ -48,22 +53,40 @@
 ///
 /// Results are accumulated into [_resultNodes].
 class _AnalyzerVisitor extends UnifyingAstVisitor<void> {
+  final TypeProvider _typeProvider;
+
   final List<ComparisonNode> _resultNodes;
 
-  _AnalyzerVisitor(this._resultNodes);
+  _AnalyzerVisitor(this._typeProvider, this._resultNodes);
 
   @override
   void visitClassDeclaration(ClassDeclaration node) {
     var children = <ComparisonNode>[];
-    _AnalyzerVisitor(children)._visitList(node.members);
+    var visitor = _AnalyzerVisitor(_typeProvider, children);
+    visitor._visitTypeParameters(node.declaredElement.typeParameters);
+    if (node.declaredElement.supertype != null) {
+      children.add(_translateType('Extends: ', node.declaredElement.supertype));
+    }
+    for (int i = 0; i < node.declaredElement.mixins.length; i++) {
+      children
+          .add(_translateType('Mixin $i: ', node.declaredElement.mixins[i]));
+    }
+    for (int i = 0; i < node.declaredElement.interfaces.length; i++) {
+      children.add(_translateType(
+          'Implements $i: ', node.declaredElement.interfaces[i]));
+    }
+    visitor._visitList(node.members);
     _resultNodes
         .add(ComparisonNode.sorted('Class ${node.name.name}', children));
   }
 
   @override
   void visitConstructorDeclaration(ConstructorDeclaration node) {
-    _resultNodes
-        .add(ComparisonNode('Constructor ${node.name?.name ?? '(unnamed)'}'));
+    var children = <ComparisonNode>[];
+    var visitor = _AnalyzerVisitor(_typeProvider, children);
+    visitor._visitParameters(node.parameters);
+    _resultNodes.add(ComparisonNode.sorted(
+        'Constructor ${node.name?.name ?? '(unnamed)'}', children));
   }
 
   @override
@@ -91,12 +114,24 @@
       // Kernel calls top level functions "methods".
       kind = 'Method';
     }
-    _resultNodes.add(ComparisonNode('$kind ${node.name.name}'));
+    var children = <ComparisonNode>[];
+    var visitor = _AnalyzerVisitor(_typeProvider, children);
+    visitor._visitTypeParameters(node.declaredElement.typeParameters);
+    visitor._visitParameters(node.functionExpression.parameters);
+    children
+        .add(_translateType('Return type: ', node.declaredElement.returnType));
+    _resultNodes
+        .add(ComparisonNode.sorted('$kind ${node.name.name}', children));
   }
 
   @override
   void visitFunctionTypeAlias(FunctionTypeAlias node) {
-    _resultNodes.add(ComparisonNode('Typedef ${node.name.name}'));
+    _visitTypedef(node);
+  }
+
+  @override
+  void visitGenericTypeAlias(GenericTypeAlias node) {
+    _visitTypedef(node);
   }
 
   @override
@@ -111,7 +146,14 @@
     } else {
       kind = 'Method';
     }
-    _resultNodes.add(ComparisonNode('$kind ${node.name.name}'));
+    var children = <ComparisonNode>[];
+    var visitor = _AnalyzerVisitor(_typeProvider, children);
+    visitor._visitTypeParameters(node.declaredElement.typeParameters);
+    visitor._visitParameters(node.parameters);
+    children
+        .add(_translateType('Return type: ', node.declaredElement.returnType));
+    _resultNodes
+        .add(ComparisonNode.sorted('$kind ${node.name.name}', children));
   }
 
   @override
@@ -127,16 +169,98 @@
   @override
   void visitVariableDeclarationList(VariableDeclarationList node) {
     for (var variableDeclaration in node.variables) {
+      var children = <ComparisonNode>[];
+      children.add(
+          _translateType('Type: ', variableDeclaration.declaredElement.type));
       // Kernel calls both fields and top level variable declarations "fields".
-      _resultNodes
-          .add(ComparisonNode('Field ${variableDeclaration.name.name}'));
+      _resultNodes.add(ComparisonNode.sorted(
+          'Field ${variableDeclaration.name.name}', children));
     }
   }
 
+  /// Converts the analyzer representation of a type into a ComparisonNode.
+  ComparisonNode _translateType(String prefix, DartType type) {
+    if (type is InterfaceType) {
+      var children = <ComparisonNode>[];
+      children
+          .add(ComparisonNode('Library: ${type.element.librarySource.uri}'));
+      for (int i = 0; i < type.typeArguments.length; i++) {
+        children.add(_translateType('Type arg $i: ', type.typeArguments[i]));
+      }
+      return ComparisonNode('${prefix}InterfaceType ${type.name}', children);
+    }
+    if (type is TypeParameterType) {
+      // TODO(paulberry): disambiguate if needed.
+      return ComparisonNode('${prefix}TypeParameterType: ${type.name}');
+    }
+    if (type.isDynamic) {
+      return ComparisonNode('${prefix}Dynamic');
+    }
+    if (type.isVoid) {
+      return ComparisonNode('${prefix}Void');
+    }
+    if (type is FunctionType) {
+      var children = <ComparisonNode>[];
+      var visitor = _AnalyzerVisitor(_typeProvider, children);
+      visitor._visitTypeParameters(type.typeFormals);
+      int positionalParameterIndex = 0;
+      for (var parameterElement in type.parameters) {
+        var kind = parameterElement.isNotOptional
+            ? 'Required'
+            : parameterElement.isOptionalPositional ? 'Optional' : 'Named';
+        var name = parameterElement.isNamed
+            ? parameterElement.name
+            : '${positionalParameterIndex++}';
+        children.add(
+            _translateType('$kind parameter $name: ', parameterElement.type));
+      }
+      return ComparisonNode.sorted('${prefix}FunctionType', children);
+    }
+    throw new UnimplementedError('_translateType: ${type.runtimeType}');
+  }
+
   /// Visits all the nodes in [nodes].
   void _visitList(List<AstNode> nodes) {
     for (var astNode in nodes) {
       astNode.accept(this);
     }
   }
+
+  void _visitParameters(FormalParameterList parameters) {
+    var children = <ComparisonNode>[];
+    // Note: parameters == null for getters
+    if (parameters != null) {
+      for (var parameter in parameters.parameters) {
+        var element = parameter.declaredElement;
+        var kind = element.isNotOptional
+            ? 'Required'
+            : element.isOptionalPositional ? 'Optional' : 'Named';
+        var parameterChildren = <ComparisonNode>[];
+        parameterChildren.add(_translateType('Type: ', element.type));
+        children.add(
+            ComparisonNode.sorted('$kind: ${element.name}', parameterChildren));
+      }
+    }
+    _resultNodes.add(ComparisonNode('Parameters', children));
+  }
+
+  void _visitTypedef(TypeAlias node) {
+    var children = <ComparisonNode>[];
+    var visitor = _AnalyzerVisitor(_typeProvider, children);
+    GenericTypeAliasElement element = node.declaredElement;
+    visitor._visitTypeParameters(element.typeParameters);
+    children.add(_translateType('Type: ', element.function.type));
+    _resultNodes
+        .add(ComparisonNode.sorted('Typedef ${node.name.name}', children));
+  }
+
+  void _visitTypeParameters(List<TypeParameterElement> typeParameters) {
+    for (int i = 0; i < typeParameters.length; i++) {
+      _resultNodes.add(ComparisonNode(
+          'Type parameter $i: ${typeParameters[i].name}', [
+        _translateType(
+            'Bound: ', typeParameters[i].bound ?? _typeProvider.objectType)
+      ]));
+    }
+  }
 }
diff --git a/pkg/analyzer_fe_comparison/lib/src/kernel.dart b/pkg/analyzer_fe_comparison/lib/src/kernel.dart
index 638447c..935dd2c 100644
--- a/pkg/analyzer_fe_comparison/lib/src/kernel.dart
+++ b/pkg/analyzer_fe_comparison/lib/src/kernel.dart
@@ -29,26 +29,36 @@
     ..embedSourceText = false;
 
   var component = await kernelForComponent(inputs, compilerOptions);
-  return component.accept(new _KernelVisitor(inputs.toSet()));
+  var libraryNodes = <ComparisonNode>[];
+  var visitor = _KernelVisitor(libraryNodes);
+  for (var library in component.libraries) {
+    if (inputs.contains(library.importUri)) {
+      library.accept(visitor);
+    }
+  }
+  return ComparisonNode.sorted('Component', libraryNodes);
 }
 
 /// Visitor for serializing a kernel representation of a program into
 /// ComparisonNodes.
-class _KernelVisitor extends TreeVisitor<ComparisonNode> {
-  final Set<Uri> _inputs;
+///
+/// Results are accumulated into [_resultNodes].
+class _KernelVisitor extends TreeVisitor<void> {
+  final List<ComparisonNode> _resultNodes;
 
-  _KernelVisitor(this._inputs);
+  _KernelVisitor(this._resultNodes);
 
   @override
-  ComparisonNode defaultTreeNode(TreeNode node) {
+  void defaultTreeNode(TreeNode node) {
     throw new UnimplementedError('KernelVisitor: ${node.runtimeType}');
   }
 
   @override
-  ComparisonNode visitClass(Class class_) {
+  void visitClass(Class class_) {
     if (class_.isAnonymousMixin) return null;
     var kind = class_.isEnum ? 'Enum' : 'Class';
     var children = <ComparisonNode>[];
+    var visitor = _KernelVisitor(children);
     if (class_.isEnum) {
       for (var field in class_.fields) {
         if (!field.isStatic) continue;
@@ -57,56 +67,99 @@
         children.add(ComparisonNode('EnumValue ${field.name.name}'));
       }
     } else {
-      _visitList(class_.fields, children);
-      _visitList(class_.constructors, children);
-      _visitList(class_.procedures, children);
+      visitor._visitTypeParameters(class_.typeParameters);
+      if (class_.supertype != null) {
+        var declaredSupertype = class_.supertype.asInterfaceType;
+        var mixedInTypes = <DartType>[];
+        while (declaredSupertype.classNode.isAnonymousMixin) {
+          // Since we're walking from the class to its declared supertype, we
+          // encounter the mixins in the reverse order that they were declared,
+          // so we have to use [List.insert] to add them to [mixedInTypes].
+          mixedInTypes.insert(
+              0, declaredSupertype.classNode.mixedInType.asInterfaceType);
+          declaredSupertype =
+              declaredSupertype.classNode.supertype.asInterfaceType;
+        }
+        children.add(_TypeVisitor.translate('Extends: ', declaredSupertype));
+        for (int i = 0; i < mixedInTypes.length; i++) {
+          children.add(_TypeVisitor.translate('Mixin $i: ', mixedInTypes[i]));
+        }
+      }
+      for (int i = 0; i < class_.implementedTypes.length; i++) {
+        children.add(_TypeVisitor.translate(
+            'Implements $i: ', class_.implementedTypes[i].asInterfaceType));
+      }
+      visitor._visitList(class_.fields);
+      visitor._visitList(class_.constructors);
+      visitor._visitList(class_.procedures);
     }
     // TODO(paulberry): handle more fields from Class
-    return ComparisonNode.sorted('$kind ${class_.name}', children);
+    _resultNodes.add(ComparisonNode.sorted('$kind ${class_.name}', children));
   }
 
   @override
-  ComparisonNode visitComponent(Component component) {
-    var children = <ComparisonNode>[];
-    _visitList(component.libraries, children);
-    return ComparisonNode.sorted('Component', children);
-  }
-
-  @override
-  ComparisonNode visitConstructor(Constructor constructor) {
+  void visitConstructor(Constructor constructor) {
     if (constructor.isSynthetic) return null;
     var name = constructor.name.name;
     if (name.isEmpty) {
       name = '(unnamed)';
     }
-    // TODO(paulberry): handle fields from Constructor
-    return ComparisonNode('Constructor $name');
+    var children = <ComparisonNode>[];
+    var visitor = _KernelVisitor(children);
+    constructor.function.accept(visitor);
+    // TODO(paulberry): handle more fields from Constructor
+    _resultNodes.add(ComparisonNode.sorted('Constructor $name', children));
   }
 
   @override
-  ComparisonNode visitField(Field field) {
+  void visitField(Field field) {
     if (field.name.name == '_redirecting#') return null;
-    // TODO(paulberry): handle fields from Field
-    return ComparisonNode('Field ${field.name.name}');
+    var children = <ComparisonNode>[];
+    children.add(_TypeVisitor.translate('Type: ', field.type));
+    // TODO(paulberry): handle more fields from Field
+    _resultNodes
+        .add(ComparisonNode.sorted('Field ${field.name.name}', children));
   }
 
   @override
-  ComparisonNode visitLibrary(Library library) {
-    if (!_inputs.contains(library.importUri)) return null;
+  void visitFunctionNode(FunctionNode node) {
+    var parent = node.parent;
+    if (!(parent is Constructor || parent is Procedure && parent.isFactory)) {
+      _visitTypeParameters(node.typeParameters);
+      _resultNodes
+          .add(_TypeVisitor.translate('Return type: ', node.returnType));
+    }
+    var parameterChildren = <ComparisonNode>[];
+    var parameterVisitor = _KernelVisitor(parameterChildren);
+    for (int i = 0; i < node.positionalParameters.length; i++) {
+      parameterVisitor._visitParameter(node.positionalParameters[i],
+          i < node.requiredParameterCount ? 'Required' : 'Optional');
+    }
+    for (int i = 0; i < node.namedParameters.length; i++) {
+      parameterVisitor._visitParameter(node.namedParameters[i], 'Named');
+    }
+    _resultNodes.add(ComparisonNode('Parameters', parameterChildren));
+    // TODO(paulberry): handle more fields from FunctionNode
+  }
+
+  @override
+  void visitLibrary(Library library) {
     var children = <ComparisonNode>[];
     if (library.name != null) {
       children.add(ComparisonNode('name=${library.name}'));
     }
-    _visitList(library.typedefs, children);
-    _visitList(library.classes, children);
-    _visitList(library.procedures, children);
-    _visitList(library.fields, children);
+    var visitor = _KernelVisitor(children);
+    visitor._visitList(library.typedefs);
+    visitor._visitList(library.classes);
+    visitor._visitList(library.procedures);
+    visitor._visitList(library.fields);
     // TODO(paulberry): handle more fields from Library
-    return ComparisonNode.sorted(library.importUri.toString(), children);
+    _resultNodes
+        .add(ComparisonNode.sorted(library.importUri.toString(), children));
   }
 
   @override
-  ComparisonNode visitProcedure(Procedure procedure) {
+  void visitProcedure(Procedure procedure) {
     if (procedure.isForwardingStub) return null;
     // TODO(paulberry): add an annotation to the ComparisonNode when the
     // procedure is a factory.
@@ -117,24 +170,108 @@
     if (name.isEmpty) {
       name = '(unnamed)';
     }
-    // TODO(paulberry): handle fields from Procedure
-    return ComparisonNode('$kind $name');
+    var children = <ComparisonNode>[];
+    var visitor = _KernelVisitor(children);
+    procedure.function.accept(visitor);
+    // TODO(paulberry): handle more fields from Procedure
+    _resultNodes.add(ComparisonNode.sorted('$kind $name', children));
   }
 
   @override
-  ComparisonNode visitTypedef(Typedef typedef) {
-    // TODO(paulberry): handle fields from Typedef
-    return ComparisonNode('Typedef ${typedef.name}');
+  void visitTypedef(Typedef typedef) {
+    var children = <ComparisonNode>[];
+    var visitor = _KernelVisitor(children);
+    visitor._visitTypeParameters(typedef.typeParameters);
+    children.add(_TypeVisitor.translate('Type: ', typedef.type));
+    // TODO(paulberry): handle more fields from Typedef
+    _resultNodes
+        .add(ComparisonNode.sorted('Typedef ${typedef.name}', children));
   }
 
-  /// Transforms all the nodes in [src] to [ComparisonNode]s, and adds those
-  /// with non-null results to [dst].
-  void _visitList(List<TreeNode> src, List<ComparisonNode> dst) {
-    for (var item in src) {
-      ComparisonNode result = item.accept(this);
-      if (result != null) {
-        dst.add(result);
-      }
+  /// Visits all the nodes in [nodes].
+  void _visitList(List<TreeNode> nodes) {
+    for (var node in nodes) {
+      node.accept(this);
     }
   }
+
+  void _visitParameter(VariableDeclaration parameter, String kind) {
+    var children = <ComparisonNode>[];
+    children.add(_TypeVisitor.translate('Type: ', parameter.type));
+    // TODO(paulberry): handle more fields from VariableDeclaration
+    _resultNodes
+        .add(ComparisonNode.sorted('$kind: ${parameter.name}', children));
+  }
+
+  void _visitTypeParameters(List<TypeParameter> typeParameters) {
+    for (int i = 0; i < typeParameters.length; i++) {
+      _resultNodes.add(ComparisonNode(
+          'Type parameter $i: ${typeParameters[i].name}',
+          [_TypeVisitor.translate('Bound: ', typeParameters[i].bound)]));
+    }
+  }
+}
+
+/// Visitor for serializing a kernel representation of a type into
+/// ComparisonNodes.
+class _TypeVisitor extends DartTypeVisitor<ComparisonNode> {
+  /// Text to prepend to the node text.
+  String _prefix;
+
+  _TypeVisitor(this._prefix);
+
+  @override
+  ComparisonNode defaultDartType(DartType node) {
+    throw new UnimplementedError('_TypeVisitor: ${node.runtimeType}');
+  }
+
+  @override
+  ComparisonNode visitDynamicType(DynamicType node) {
+    return ComparisonNode('${_prefix}Dynamic');
+  }
+
+  @override
+  ComparisonNode visitFunctionType(FunctionType node) {
+    var children = <ComparisonNode>[];
+    var visitor = _KernelVisitor(children);
+    visitor._visitTypeParameters(node.typeParameters);
+    for (int i = 0; i < node.positionalParameters.length; i++) {
+      var kind = i < node.requiredParameterCount ? 'Required' : 'Optional';
+      children
+          .add(translate('$kind parameter $i: ', node.positionalParameters[i]));
+    }
+    for (var namedType in node.namedParameters) {
+      children
+          .add(translate('Named parameter ${namedType.name}', namedType.type));
+    }
+    return ComparisonNode.sorted('${_prefix}FunctionType', children);
+  }
+
+  @override
+  ComparisonNode visitInterfaceType(InterfaceType node) {
+    var children = <ComparisonNode>[];
+    children.add(ComparisonNode(
+        'Library: ${node.classNode.enclosingLibrary.importUri}'));
+    for (int i = 0; i < node.typeArguments.length; i++) {
+      children.add(translate('Type arg $i: ', node.typeArguments[i]));
+    }
+    return ComparisonNode(
+        '${_prefix}InterfaceType ${node.classNode.name}', children);
+  }
+
+  @override
+  ComparisonNode visitTypeParameterType(TypeParameterType node) {
+    // TODO(paulberry): disambiguate if needed.
+    return ComparisonNode(
+        '${_prefix}TypeParameterType: ${node.parameter.name}');
+  }
+
+  @override
+  ComparisonNode visitVoidType(VoidType node) {
+    return ComparisonNode('${_prefix}Void');
+  }
+
+  static ComparisonNode translate(String prefix, DartType type) {
+    return type.accept(new _TypeVisitor(prefix));
+  }
 }